Merge "Turn off the verbose logging in the AppBatteryTracker" into tm-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index f49cdbf..4710322 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1777,7 +1777,10 @@
* {@link Build.VERSION_CODES#S}, but starting from Android version
* {@link Build.VERSION_CODES#TIRAMISU}, expedited jobs for the foreground app are
* guaranteed to be started before {@link JobScheduler#schedule(JobInfo)} returns (assuming
- * all requested constraints are satisfied), similar to foreground services.
+ * all requested constraints are satisfied), similar to foreground services. However, this
+ * start guarantee means there is a higher chance of overlapping executions, as noted in
+ * {@link JobService}, so be sure to handle that properly if you intend to reschedule the
+ * job while it's actively running.
*
* @see JobInfo#isExpedited()
*/
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 1f4ef04..388bbf1 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -107,7 +107,9 @@
/**
* Schedule a job to be executed. Will replace any currently scheduled job with the same
* ID with the new information in the {@link JobInfo}. If a job with the given ID is currently
- * running, it will be stopped.
+ * running, it will be stopped. Note that in some cases, the newly scheduled job may be started
+ * before the previously running job has been fully stopped. See {@link JobService} for
+ * additional details.
*
* <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
* rescheduling the same job and the job didn't execute, especially on platform versions before
@@ -131,7 +133,7 @@
* job. If a job with the same ID is already scheduled, it will be replaced with the
* new {@link JobInfo}, but any previously enqueued work will remain and be dispatched the
* next time it runs. If a job with the same ID is already running, the new work will be
- * enqueued for it.
+ * enqueued for it without stopping the job.
*
* <p>The work you enqueue is later retrieved through
* {@link JobParameters#dequeueWork() JobParameters.dequeueWork}. Be sure to see there
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index e5b0742..7ed4b62 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -31,6 +31,17 @@
* in blocking any future callbacks from the JobManager - specifically
* {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
* scheduling requirements are no longer being met.</p>
+ *
+ * As a subclass of {@link Service}, there will only be one active instance of any JobService
+ * subclasses, regardless of job ID. This means that if you schedule multiple jobs with different
+ * job IDs but using the same JobService class, that JobService may receive multiple calls to
+ * {@link #onStartJob(JobParameters)} and {@link #onStopJob(JobParameters)}, with each call being
+ * for the separate jobs.
+ *
+ * <p class="note">Note that if you cancel and reschedule an already executing job,
+ * there may be a small period of time where {@link #onStartJob(JobParameters)} has been called for
+ * the newly scheduled job instance before {@link #onStopJob(JobParameters)} has been called or
+ * fully processed for the old job.</p>
*/
public abstract class JobService extends Service {
private static final String TAG = "JobService";
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index c86353c..afe36b5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -542,6 +542,22 @@
return mRunningJobs.contains(job);
}
+ /**
+ * Returns true if a job that is "similar" to the provided job is currently running.
+ * "Similar" in this context means any job that the {@link JobStore} would consider equivalent
+ * and replace one with the other.
+ */
+ @GuardedBy("mLock")
+ private boolean isSimilarJobRunningLocked(JobStatus job) {
+ for (int i = mRunningJobs.size() - 1; i >= 0; --i) {
+ JobStatus js = mRunningJobs.valueAt(i);
+ if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Return {@code true} if the state was updated. */
@GuardedBy("mLock")
private boolean refreshSystemStateLocked() {
@@ -699,6 +715,21 @@
continue;
}
+ final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
+ && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
+ // Avoid overlapping job execution as much as possible.
+ if (!isTopEj && isSimilarJobRunningLocked(nextPending)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Delaying execution of job because of similarly running one: "
+ + nextPending);
+ }
+ // It would be nice to let the JobService running the other similar job know about
+ // this new job so that it doesn't unbind from the JobService and we can call
+ // onStartJob as soon as the older job finishes.
+ // TODO: optimize the job reschedule flow to reduce service binding churn
+ continue;
+ }
+
// Find an available slot for nextPending. The context should be one of the following:
// 1. Unused
// 2. Its job should have used up its minimum execution guarantee so it
@@ -707,8 +738,6 @@
ContextAssignment selectedContext = null;
final int allWorkTypes = getJobWorkTypes(nextPending);
final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
- final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
- && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
final boolean isInOverage = projectedRunningCount > STANDARD_CONCURRENCY_LIMIT;
boolean startingJob = false;
if (idle.size() > 0) {
@@ -1177,6 +1206,15 @@
continue;
}
+ // Avoid overlapping job execution as much as possible.
+ if (isSimilarJobRunningLocked(nextPending)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Avoiding execution of job because of similarly running one: "
+ + nextPending);
+ }
+ continue;
+ }
+
if (worker.getPreferredUid() != nextPending.getUid()) {
if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
int allWorkTypes = getJobWorkTypes(nextPending);
@@ -1260,6 +1298,15 @@
continue;
}
+ // Avoid overlapping job execution as much as possible.
+ if (isSimilarJobRunningLocked(nextPending)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Avoiding execution of job because of similarly running one: "
+ + nextPending);
+ }
+ continue;
+ }
+
if (isPkgConcurrencyLimitedLocked(nextPending)) {
continue;
}
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 358c327..cd70e88 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1208,12 +1208,22 @@
// This may throw a SecurityException.
jobStatus.prepareLocked();
+ final boolean canExecuteImmediately;
if (toCancel != null) {
// Implicitly replaces the existing job record with the new instance
- cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
- JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app");
+ final boolean wasJobExecuting = cancelJobImplLocked(toCancel, jobStatus,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP,
+ JobParameters.INTERNAL_STOP_REASON_CANCELED,
+ "job rescheduled by app");
+ // Avoid overlapping job executions. Don't push for immediate execution if an old
+ // job with the same ID was running, but let TOP EJs start immediately.
+ canExecuteImmediately = !wasJobExecuting
+ || (jobStatus.isRequestedExpeditedJob()
+ && mUidBiasOverride.get(jobStatus.getSourceUid(), JobInfo.BIAS_DEFAULT)
+ == JobInfo.BIAS_TOP_APP);
} else {
startTrackingJobLocked(jobStatus, null);
+ canExecuteImmediately = true;
}
if (work != null) {
@@ -1256,7 +1266,12 @@
// list and try to run it.
mJobPackageTracker.notePending(jobStatus);
mPendingJobQueue.add(jobStatus);
- maybeRunPendingJobsLocked();
+ if (canExecuteImmediately) {
+ // Don't ask the JobConcurrencyManager to try to run the job immediately. The
+ // JobServiceContext will ask the JobConcurrencyManager for another job once
+ // it finishes cleaning up the old job.
+ maybeRunPendingJobsLocked();
+ }
} else {
evaluateControllerStatesLocked(jobStatus);
}
@@ -1377,8 +1392,10 @@
* is null, the cancelled job is removed outright from the system. If
* {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
* currently scheduled jobs.
+ *
+ * @return true if the cancelled job was running
*/
- private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
+ private boolean cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
@JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked();
@@ -1389,7 +1406,7 @@
}
mChangedJobList.remove(cancelled);
// Cancel if running.
- mConcurrencyManager.stopJobOnServiceContextLocked(
+ boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked(
cancelled, reason, internalReasonCode, debugReason);
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -1397,6 +1414,7 @@
startTrackingJobLocked(incomingJob, cancelled);
}
reportActiveLocked();
+ return wasRunning;
}
void updateUidState(int uid, int procState) {
@@ -1755,7 +1773,7 @@
// same job ID), we remove it from the JobStore and tell the JobServiceContext to stop
// running the job. Once the job stops running, we then call this method again.
// TODO: rework code so we don't intentionally call this method twice for the same job
- Slog.w(TAG, "Job didn't exist in JobStore");
+ Slog.w(TAG, "Job didn't exist in JobStore: " + jobStatus.toShortString());
}
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
@@ -3813,6 +3831,7 @@
// Double indent for readability
pw.increaseIndent();
pw.increaseIndent();
+ pw.println(job.toShortString());
job.dump(pw, true, nowElapsed);
pw.decreaseIndent();
pw.decreaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 80f3fea..c90291e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -979,6 +979,7 @@
dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime);
idpw.print(" lastJob=");
TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
+ idpw.print(" lastInformedBucket=" + appUsageHistory.lastInformedBucket);
if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) {
idpw.print(" lastRestrictAttempt=");
TimeUtils.formatDuration(
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3d6b73..1891e06 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1155,6 +1155,12 @@
final int appId = getAppId(packageName);
if (appId < 0) return;
+ final int minBucket = getAppMinBucket(packageName, appId, userId);
+ if (idle && minBucket < AppIdleHistory.IDLE_BUCKET_CUTOFF) {
+ Slog.e(TAG, "Tried to force an app to be idle when its min bucket is "
+ + standbyBucketToString(minBucket));
+ return;
+ }
final long elapsedRealtime = mInjector.elapsedRealtime();
final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
@@ -1166,12 +1172,10 @@
final boolean stillIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
// Inform listeners if necessary
+ maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
+ REASON_MAIN_FORCED_BY_USER, false);
if (previouslyIdle != stillIdle) {
- maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
- REASON_MAIN_FORCED_BY_USER, false);
- if (!stillIdle) {
- notifyBatteryStats(packageName, userId, idle);
- }
+ notifyBatteryStats(packageName, userId, stillIdle);
}
}
@@ -1934,6 +1938,8 @@
}
mAppIdleHistory.setAppStandbyBucket(
packageName, userId, elapsedRealtime, newBucket, newReason);
+ maybeInformListeners(packageName, userId, elapsedRealtime, newBucket,
+ newReason, false);
}
}
@@ -2490,6 +2496,8 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INFORM_LISTENERS:
+ // TODO(230875908): Properly notify BatteryStats when apps change from active to
+ // idle, and vice versa
StandbyUpdateRecord r = (StandbyUpdateRecord) msg.obj;
informListeners(r.packageName, r.userId, r.bucket, r.reason,
r.isUserInteraction);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4681d49..d6a067d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -919,7 +919,7 @@
field public int id;
field public String lastLoggedInFingerprint;
field public long lastLoggedInTime;
- field public String name;
+ field @Nullable public String name;
field public boolean partial;
field public boolean preCreated;
field public int profileBadge;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 974d20a..e820733 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7770,10 +7770,11 @@
* user will always see the normal notification view.
*
* <p>
- * If the app is targeting Android P and above, it is required to use the {@link Person}
- * class in order to get an optimal rendering of the notification and its avatars. For
- * conversations involving multiple people, the app should also make sure that it marks the
- * conversation as a group with {@link #setGroupConversation(boolean)}.
+ * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is
+ * required to use the {@link Person} class in order to get an optimal rendering of the
+ * notification and its avatars. For conversations involving multiple people, the app should
+ * also make sure that it marks the conversation as a group with
+ * {@link #setGroupConversation(boolean)}.
*
* <p>
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
@@ -7846,8 +7847,8 @@
* @param user Required - The person displayed for any messages that are sent by the
* user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
* who don't have a Person associated with it will be displayed as if they were sent
- * by this user. The user also needs to have a valid name associated with it, which will
- * be enforced starting in Android P.
+ * by this user. The user also needs to have a valid name associated with it, which is
+ * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}.
*/
public MessagingStyle(@NonNull Person user) {
mUser = user;
@@ -7904,9 +7905,9 @@
/**
* Sets the title to be displayed on this conversation. May be set to {@code null}.
*
- * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a
- * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this
- * case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
+ * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
+ * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
+ * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
* {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
* instead.
*
@@ -8065,7 +8066,7 @@
}
/**
- * Gets the list of {@code Message} objects that represent the notification
+ * Gets the list of {@code Message} objects that represent the notification.
*/
public List<Message> getMessages() {
return mMessages;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index a8ae191..32207af 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,19 +110,6 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
}
],
"file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index ea30ef7..c8033fa 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -863,12 +863,6 @@
PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN";
/**
- * Title for selecting device admin apps
- */
- public static final String SELECT_DEVICE_ADMIN_APPS =
- PREFIX + "SELECT_DEVICE_ADMIN_APPS";
-
- /**
* Message when there are no available device admin apps to display
*/
public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS";
@@ -910,11 +904,6 @@
PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING";
/**
- * Title for screen to set a profile owner
- */
- public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE";
-
- /**
* Simplified title for dialog to set a profile owner
*/
public static final String SET_PROFILE_OWNER_DIALOG_TITLE =
@@ -1173,7 +1162,93 @@
/**
* Header for items under the personal user
*/
- public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "category_personal";
+ public static final String PERSONAL_CATEGORY_HEADER =
+ PREFIX + "PERSONAL_CATEGORY_HEADER";
+
+ /**
+ * Text to indicate work notification content will be shown on the lockscreen.
+ */
+ public static final String LOCK_SCREEN_SHOW_WORK_NOTIFICATION_CONTENT =
+ PREFIX + "LOCK_SCREEN_SHOW_WORK_NOTIFICATION_CONTENT";
+
+ /**
+ * Text to indicate work notification content will be shown on the lockscreen.
+ */
+ public static final String LOCK_SCREEN_HIDE_WORK_NOTIFICATION_CONTENT =
+ PREFIX + "LOCK_SCREEN_HIDE_WORK_NOTIFICATION_CONTENT";
+
+ /**
+ * Text for toggle to enable auto-sycing personal data
+ */
+ public static final String AUTO_SYNC_PERSONAL_DATA = PREFIX
+ + "AUTO_SYNC_PERSONAL_DATA";
+
+ /**
+ * Text for toggle to enable auto-sycing work data
+ */
+ public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA";
+
+ /**
+ * Summary for "More security settings" section when a work profile is on the device.
+ */
+ public static final String MORE_SECURITY_SETTINGS_WORK_PROFILE_SUMMARY = PREFIX
+ + "MORE_SECURITY_SETTINGS_WORK_PROFILE_SUMMARY";
+
+ /**
+ * Title for screen asking the user to choose a type of screen lock (such as a pattern,
+ * PIN, or password) that they need to enter to use their work apps
+ */
+ public static final String LOCK_SETTINGS_NEW_PROFILE_LOCK_TITLE = PREFIX
+ + "LOCK_SETTINGS_NEW_PROFILE_LOCK_TITLE";
+
+ /**
+ * Title for screen asking the user to update the type of screen lock (such as a
+ * pattern, PIN, or password) that they need to enter to use their work apps
+ */
+ public static final String LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE = PREFIX
+ + "LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE";
+
+ /**
+ * Title for section listing information that can be seen by organization
+ */
+ public static final String INFORMATION_SEEN_BY_ORGANIZATION_TITLE = PREFIX
+ + "INFORMATION_SEEN_BY_ORGANIZATION_TITLE";
+
+ /**
+ * Title for section listing changes made by the organization.
+ */
+ public static final String CHANGES_BY_ORGANIZATION_TITLE =
+ PREFIX + "CHANGES_BY_ORGANIZATION_TITLE";
+
+ /**
+ * Footer for enterprise privacy screen.
+ */
+ public static final String ENTERPRISE_PRIVACY_FOOTER =
+ PREFIX + "ENTERPRISE_PRIVACY_FOOTER";
+
+ /**
+ * Title for spell checker settings for work.
+ */
+ public static final String SPELL_CHECKER_FOR_WORK =
+ PREFIX + "SPELL_CHECKER_FOR_WORK";
+
+ /**
+ * Title for personal dictionary for work settings.
+ */
+ public static final String PERSONAL_DICTIONARY_FOR_WORK =
+ PREFIX + "PERSONAL_DICTIONARY_FOR_WORK";
+
+ /**
+ * Summary for switch preference to indicate it is disabled by the admin
+ */
+ public static final String DISABLED_BY_ADMIN_SWITCH_SUMMARY =
+ PREFIX + "DISABLED_BY_ADMIN_SWITCH_SUMMARY";
+
+ /**
+ * Summary for switch preference to indicate it is enabled by the admin
+ */
+ public static final String ENABLED_BY_ADMIN_SWITCH_SUMMARY =
+ PREFIX + "ENABLED_BY_ADMIN_SWITCH_SUMMARY";
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4d56c1d..907db7d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -513,10 +513,23 @@
* restart. There is no guarantee this will be respected, as the system
* tries to balance such requests from one app vs. the importance of
* keeping other apps around.
+ *
+ * @deprecated Repurposed to {@link #BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE}.
*/
+ @Deprecated
public static final int BIND_VISIBLE = 0x10000000;
/**
+ * @hide Flag for {@link #bindService}: Treat the binding as hosting a foreground service
+ * and also visible to the user. That is, the app hosting the service will get its process state
+ * bumped to the {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE},
+ * and it's considered as visible to the user, thus less likely to be expunged from memory
+ * on low memory situations. This is intented for use by processes with the process state
+ * better than the {@link android.app.ActivityManager#PROCESS_STATE_TOP}.
+ */
+ public static final int BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE = 0x10000000;
+
+ /**
* @hide
* Flag for {@link #bindService}: Consider this binding to be causing the target
* process to be showing UI, so it will be do a UI_HIDDEN memory trim when it goes
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 76e9fcb..d6e13ac 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
@@ -170,7 +171,7 @@
@UnsupportedAppUsage
public int serialNumber;
@UnsupportedAppUsage
- public String name;
+ public @Nullable String name;
@UnsupportedAppUsage
public String iconPath;
@UnsupportedAppUsage
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index a392afd..9cf329c 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -50,5 +50,7 @@
void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
boolean suppress);
+ boolean requiresAuthentication();
+
void showSensorUseDialog(int sensor);
}
\ No newline at end of file
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 0460e58..99b58c9 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -327,6 +327,8 @@
@NonNull
private boolean mToggleListenerRegistered = false;
+ private Boolean mRequiresAuthentication = null;
+
/**
* Private constructor to ensure only a single instance is created.
*/
@@ -761,6 +763,23 @@
}
/**
+ * @return whether the device is required to be unlocked to change software state.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ public boolean requiresAuthentication() {
+ if (mRequiresAuthentication == null) {
+ try {
+ mRequiresAuthentication = mService.requiresAuthentication();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return mRequiresAuthentication;
+ }
+
+ /**
* If sensor privacy for the provided sensor is enabled then this call will show the user the
* dialog which is shown when an application attempts to use that sensor. If privacy isn't
* enabled then this does nothing.
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b05e6d1..a90eb88 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1058,7 +1058,7 @@
* must select one unique size from this metadata to use (e.g., preview and recording streams
* must have the same size). Otherwise, the high speed capture session creation will fail.</p>
* <p>The min and max fps will be multiple times of 30fps.</p>
- * <p>High speed video streaming extends significant performance pressue to camera hardware,
+ * <p>High speed video streaming extends significant performance pressure to camera hardware,
* to achieve efficient high speed streaming, the camera device may have to aggregate
* multiple frames together and send to camera device for processing where the request
* controls are same for all the frames in this batch. Max batch size indicates
@@ -1143,7 +1143,7 @@
* <p>Range of boosts for {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} supported
* by this camera device.</p>
* <p>Devices support post RAW sensitivity boost will advertise
- * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controling
+ * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controlling
* post RAW sensitivity boost.</p>
* <p>This key will be <code>null</code> for devices that do not support any RAW format
* outputs. For devices that do support RAW format outputs, this key will always
@@ -1323,7 +1323,7 @@
* <p>Maximum flashlight brightness level.</p>
* <p>If this value is greater than 1, then the device supports controlling the
* flashlight brightness level via
- * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.
+ * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
* If this value is equal to 1, flashlight brightness control is not supported.
* The value for this key will be null for devices with no flash unit.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -1335,7 +1335,7 @@
/**
* <p>Default flashlight brightness level to be set via
- * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p>
+ * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.</p>
* <p>If flash unit is available this will be greater than or equal to 1 and less
* or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p>
* <p>Setting flashlight brightness above the default level
@@ -1376,7 +1376,7 @@
* camera device.</p>
* <p>This list will include at least one non-zero resolution, plus <code>(0,0)</code> for indicating no
* thumbnail should be generated.</p>
- * <p>Below condiditions will be satisfied for this size list:</p>
+ * <p>Below conditions will be satisfied for this size list:</p>
* <ul>
* <li>The sizes will be sorted by increasing pixel area (width x height).
* If several resolutions have the same area, they will be sorted by increasing width.</li>
@@ -1982,7 +1982,7 @@
* the camera device. Using more streams simultaneously may require more hardware and
* CPU resources that will consume more power. The image format for an output stream can
* be any supported format provided by android.scaler.availableStreamConfigurations.
- * The formats defined in android.scaler.availableStreamConfigurations can be catergorized
+ * The formats defined in android.scaler.availableStreamConfigurations can be categorized
* into the 3 stream types as below:</p>
* <ul>
* <li>Processed (but stalling): any non-RAW format with a stallDurations > 0.
@@ -2324,7 +2324,7 @@
* but clients should be aware and expect delays during their application.
* An example usage scenario could look like this:</p>
* <ul>
- * <li>The camera client starts by quering the session parameter key list via
+ * <li>The camera client starts by querying the session parameter key list via
* {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
* <li>Before triggering the capture session create sequence, a capture request
* must be built via
@@ -2379,7 +2379,7 @@
* {@link android.hardware.camera2.CameraCharacteristics#getKeys } that require camera clients
* to acquire the {@link android.Manifest.permission#CAMERA } permission before calling
* {@link android.hardware.camera2.CameraManager#getCameraCharacteristics }. If the
- * permission is not held by the camera client, then the values of the repsective properties
+ * permission is not held by the camera client, then the values of the respective properties
* will not be present in {@link android.hardware.camera2.CameraCharacteristics }.</p>
* <p>This key is available on all devices.</p>
* @hide
@@ -2759,7 +2759,7 @@
* </table>
* <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be
* media performance class 12 or higher by setting
- * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
+ * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
* the primary camera devices (first rear/front camera in the camera ID list) will not
* support JPEG sizes smaller than 1080p. If the application configures a JPEG stream
* smaller than 1080p, the camera device will round up the JPEG image size to at least
@@ -2833,7 +2833,7 @@
* </table>
* <p>For applications targeting SDK version 31 or newer, if the mobile device doesn't declare
* to be media performance class 12 or better by setting
- * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
+ * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
* or if the camera device isn't a primary rear/front camera, the minimum required output
* stream configurations are the same as for applications targeting SDK version older than
* 31.</p>
@@ -3485,14 +3485,16 @@
* to output different resolution images depending on the current active physical camera or
* pixel mode. With multi-resolution input streams, the camera device can reprocess images
* of different resolutions from different physical cameras or sensor pixel modes.</p>
- * <p>When set to TRUE:
- * * For a logical multi-camera, the camera framework derives
+ * <p>When set to TRUE:</p>
+ * <ul>
+ * <li>For a logical multi-camera, the camera framework derives
* {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap} by combining the
* android.scaler.physicalCameraMultiResolutionStreamConfigurations from its physical
- * cameras.
- * * For an ultra-high resolution sensor camera, the camera framework directly copies
+ * cameras.</li>
+ * <li>For an ultra-high resolution sensor camera, the camera framework directly copies
* the value of android.scaler.physicalCameraMultiResolutionStreamConfigurations to
- * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</p>
+ * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</li>
+ * </ul>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
* Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
@@ -3513,7 +3515,7 @@
* capture, video record for encoding the camera output for the purpose of future playback,
* and video call for live realtime video conferencing.</p>
* <p>With this flag, the camera device can optimize the image processing pipeline
- * parameters, such as tuning, sensor mode, and ISP settings, indepedent of
+ * parameters, such as tuning, sensor mode, and ISP settings, independent of
* the properties of the immediate camera output surface. For example, if the output
* surface is a SurfaceTexture, the stream use case flag can be used to indicate whether
* the camera frames eventually go to display, video encoder,
@@ -3535,7 +3537,7 @@
* {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
* capability is documented in the camera device
* {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline}. The
- * application is strongly recommended to use one of the guaranteed stream combintations.
+ * application is strongly recommended to use one of the guaranteed stream combinations.
* If the application creates a session with a stream combination not in the guaranteed
* list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
* the camera device may ignore some stream use cases due to hardware constraints
@@ -5209,7 +5211,7 @@
* EXTERIOR_* value.</p>
* <p>If a camera has INTERIOR_OTHER or EXTERIOR_OTHER, or more than one camera is at the
* same location and facing the same direction, their static metadata will list the
- * following entries, so that applications can determain their lenses' exact facing
+ * following entries, so that applications can determine their lenses' exact facing
* directions:</p>
* <ul>
* <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 40565b0..eb8c73a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -409,7 +409,7 @@
/**
* <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the origin of the
- * automotive sensor coodinate system, which is at the center of the rear axle.</p>
+ * automotive sensor coordinate system, which is at the center of the rear axle.</p>
*
* @see CameraCharacteristics#LENS_POSE_TRANSLATION
* @see CameraCharacteristics#LENS_POSE_REFERENCE
@@ -683,7 +683,7 @@
* captured at the same rate as the maximum-size YUV_420_888 resolution is.</p>
* <p>If the device supports the PRIVATE_REPROCESSING capability, then the same guarantees
* as for the YUV_420_888 format also apply to the {@link android.graphics.ImageFormat#PRIVATE } format.</p>
- * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranted to have a value between 0
+ * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranteed to have a value between 0
* and 4, inclusive. {@link CameraCharacteristics#CONTROL_AE_LOCK_AVAILABLE android.control.aeLockAvailable} and {@link CameraCharacteristics#CONTROL_AWB_LOCK_AVAILABLE android.control.awbLockAvailable}
* are also guaranteed to be <code>true</code> so burst capture with these two locks ON yields
* consistent image output.</p>
@@ -843,7 +843,7 @@
* {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.</li>
* <li>The FPS ranges are selected from {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li>
* </ul>
- * <p>When above conditions are NOT satistied,
+ * <p>When above conditions are NOT satisfied,
* {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }
* will fail.</p>
* <p>Switching to a FPS range that has different maximum FPS may trigger some camera device
@@ -986,7 +986,7 @@
* non-active physical cameras. For example, if the logical camera has a wide-ultrawide
* configuration where the wide lens is the default, when the crop region is set to the
* logical camera's active array size, (and the zoom ratio set to 1.0 starting from
- * Android 11), a physical stream for the ultrawide camera may prefer outputing images
+ * Android 11), a physical stream for the ultrawide camera may prefer outputting images
* with larger field-of-view than that of the wide camera for better stereo matching
* margin or more robust motion tracking. At the same time, the physical non-RAW streams'
* field of view must not be smaller than the requested crop region and zoom ratio, as
@@ -1175,21 +1175,23 @@
* when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }),
* the <code>RAW_SENSOR</code> stream will have a regular bayer pattern.</p>
- * <p>This capability requires the camera device to support the following :
- * * The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below
+ * <p>This capability requires the camera device to support the following :</p>
+ * <ul>
+ * <li>The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below
* refers to the one, described by
- * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.
- * * One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.
- * * {@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.</li>
+ * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
+ * <li>{@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input
* format, that is, {@link android.graphics.ImageFormat#RAW_SENSOR } is included in the
- * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.
- * * {@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
- * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.
- * * Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}
- * * Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate
- * drop relative to the sensor's maximum capture rate (at that resolution).
- * * No CaptureRequest controls will be applicable when a request has an input target
- * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</p>
+ * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li>
+ * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
+ * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li>
+ * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}</li>
+ * <li>Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate
+ * drop relative to the sensor's maximum capture rate (at that resolution).</li>
+ * <li>No CaptureRequest controls will be applicable when a request has an input target
+ * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</li>
+ * </ul>
*
* @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
@@ -1205,16 +1207,18 @@
* {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }.
* They can be configured as part of the capture session initialization via
* {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }.
- * Cameras that enable this capability must also support the following:
- * * Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }
- * * All mandatory stream combinations for this specific capability as per
- * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession }
- * * In case the device is not able to capture some combination of supported
+ * Cameras that enable this capability must also support the following:</p>
+ * <ul>
+ * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
+ * <li>All mandatory stream combinations for this specific capability as per
+ * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession }</li>
+ * <li>In case the device is not able to capture some combination of supported
* standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
* then those constraints must be listed in
- * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }
- * * Recommended dynamic range profile listed in
- * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</p>
+ * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }</li>
+ * <li>Recommended dynamic range profile listed in
+ * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</li>
+ * </ul>
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18;
@@ -1224,22 +1228,26 @@
* {@link android.hardware.camera2.params.OutputConfiguration#setStreamUseCase }
* so that the device can optimize camera pipeline parameters such as tuning, sensor
* mode, or ISP settings for a specific user scenario.
- * Some sample usages of this capability are:
- * * Distinguish high quality YUV captures from a regular YUV stream where
- * the image quality may not be as good as the JPEG stream, or
- * * Use one stream to serve multiple purposes: viewfinder, video recording and
+ * Some sample usages of this capability are:</p>
+ * <ul>
+ * <li>Distinguish high quality YUV captures from a regular YUV stream where
+ * the image quality may not be as good as the JPEG stream, or</li>
+ * <li>Use one stream to serve multiple purposes: viewfinder, video recording and
* still capture. This is common with applications that wish to apply edits equally
- * to preview, saved images, and saved videos.</p>
+ * to preview, saved images, and saved videos.</li>
+ * </ul>
* <p>This capability requires the camera device to support the following
- * stream use cases:
- * * DEFAULT for backward compatibility where the application doesn't set
- * a stream use case
- * * PREVIEW for live viewfinder and in-app image analysis
- * * STILL_CAPTURE for still photo capture
- * * VIDEO_RECORD for recording video clips
- * * PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video
- * recording, and still capture.
- * * VIDEO_CALL for long running video calls</p>
+ * stream use cases:</p>
+ * <ul>
+ * <li>DEFAULT for backward compatibility where the application doesn't set
+ * a stream use case</li>
+ * <li>PREVIEW for live viewfinder and in-app image analysis</li>
+ * <li>STILL_CAPTURE for still photo capture</li>
+ * <li>VIDEO_RECORD for recording video clips</li>
+ * <li>PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video
+ * recording, and still capture.</li>
+ * <li>VIDEO_CALL for long running video calls</li>
+ * </ul>
* <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES }
* lists all of the supported stream use cases.</p>
* <p>Refer to {@link android.hardware.camera2.CameraDevice#createCaptureSession } for the
@@ -1391,10 +1399,10 @@
* <p>Live stream shown to the user.</p>
* <p>Optimized for performance and usability as a viewfinder, but not necessarily for
* image quality. The output is not meant to be persisted as saved images or video.</p>
- * <p>No stall if android.control.<em> are set to FAST; may have stall if android.control.</em>
- * are set to HIGH_QUALITY. This use case has the same behavior as the default
- * SurfaceView and SurfaceTexture targets. Additionally, this use case can be used for
- * in-app image analysis.</p>
+ * <p>No stall if android.control.* are set to FAST. There may be stall if
+ * they are set to HIGH_QUALITY. This use case has the same behavior as the
+ * default SurfaceView and SurfaceTexture targets. Additionally, this use case can be
+ * used for in-app image analysis.</p>
* @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
*/
public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 0x1;
@@ -1441,7 +1449,7 @@
public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 0x4;
/**
- * <p>Long-running video call optimized for both power efficienty and video quality.</p>
+ * <p>Long-running video call optimized for both power efficiency and video quality.</p>
* <p>The camera sensor may run in a lower-resolution mode to reduce power consumption
* at the cost of some image and digital zoom quality. Unlike VIDEO_RECORD, VIDEO_CALL
* outputs are expected to work in dark conditions, so are usually accompanied with
@@ -2946,10 +2954,10 @@
* android.control.availableHighSpeedVideoConfigurations.</li>
* <li>No processed non-stalling or raw streams are configured.</li>
* </ul>
- * <p>When above conditions are NOT satistied, the controls of this mode and
+ * <p>When above conditions are NOT satisfied, the controls of this mode and
* {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} will be ignored by the camera device,
* the camera device will fall back to {@link CaptureRequest#CONTROL_MODE android.control.mode} <code>==</code> AUTO,
- * and the returned capture result metadata will give the fps range choosen
+ * and the returned capture result metadata will give the fps range chosen
* by the camera device.</p>
* <p>Switching into or out of this mode may trigger some camera ISP/sensor
* reconfigurations, which may introduce extra latency. It is recommended that
@@ -3034,7 +3042,7 @@
* if the {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange} gives range of [100, 1600],
* the camera device auto-exposure routine tuning process may limit the actual
* exposure sensitivity range to [100, 1200] to ensure that the noise level isn't
- * exessive in order to preserve the image quality. Under this situation, the image under
+ * excessive in order to preserve the image quality. Under this situation, the image under
* low light may be under-exposed when the sensor max exposure time (bounded by the
* {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of the
* ON_* modes) and effective max sensitivity are reached. This scene mode allows the
@@ -3631,7 +3639,7 @@
public static final int TONEMAP_MODE_HIGH_QUALITY = 2;
/**
- * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to peform
+ * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to perform
* tonemapping.</p>
* <p>All color enhancement and tonemapping must be disabled, except
* for applying the tonemapping curve specified by {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma}.</p>
@@ -3644,7 +3652,7 @@
/**
* <p>Use the preset tonemapping curve specified in
- * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to peform tonemapping.</p>
+ * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to perform tonemapping.</p>
* <p>All color enhancement and tonemapping must be disabled, except
* for applying the tonemapping curve specified by
* {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve}.</p>
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 15e59e0..c5cf0f6 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1767,7 +1767,7 @@
* routine is enabled, overriding the application's selected
* {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
* {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
- * is OFF, the behavior of AWB is device dependent. It is recommened to
+ * is OFF, the behavior of AWB is device dependent. It is recommended to
* also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
* setting AE mode to OFF.</p>
* <p>When set to the OFF mode, the camera device's auto-white balance
@@ -1917,13 +1917,15 @@
* strategy.</p>
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
- * <p>All intents are supported by all devices, except that:
- * * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * PRIVATE_REPROCESSING or YUV_REPROCESSING.
- * * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MANUAL_SENSOR.
- * * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MOTION_TRACKING.</p>
+ * <p>All intents are supported by all devices, except that:</p>
+ * <ul>
+ * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li>
+ * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MANUAL_SENSOR.</li>
+ * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MOTION_TRACKING.</li>
+ * </ul>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -2680,7 +2682,7 @@
* and keep jpeg and thumbnail image data unrotated.</li>
* <li>Rotate the jpeg and thumbnail image data and not set
* {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this
- * case, LIMITED or FULL hardware level devices will report rotated thumnail size in
+ * case, LIMITED or FULL hardware level devices will report rotated thumbnail size in
* capture result, so the width and height will be interchanged if 90 or 270 degree
* orientation is requested. LEGACY device will always report unrotated thumbnail
* size.</li>
@@ -3806,9 +3808,11 @@
/**
* <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* GAMMA_VALUE</p>
- * <p>The tonemap curve will be defined the following formula:
- * * OUT = pow(IN, 1.0 / gamma)
- * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * <p>The tonemap curve will be defined the following formula:</p>
+ * <ul>
+ * <li>OUT = pow(IN, 1.0 / gamma)</li>
+ * </ul>
+ * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
* pow is the power function and gamma is the gamma value specified by this
* key.</p>
* <p>The same curve will be applied to all color channels. The camera device
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1faec5b..3e1deb2 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -1173,7 +1173,7 @@
* <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
* <td align="center">CONVERGED</td>
- * <td align="center">Converged after a precapture sequenceis canceled, transient states are skipped by camera device.</td>
+ * <td align="center">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
* </tr>
* <tr>
* <td align="center">CONVERGED</td>
@@ -1847,7 +1847,7 @@
* routine is enabled, overriding the application's selected
* {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
* {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
- * is OFF, the behavior of AWB is device dependent. It is recommened to
+ * is OFF, the behavior of AWB is device dependent. It is recommended to
* also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
* setting AE mode to OFF.</p>
* <p>When set to the OFF mode, the camera device's auto-white balance
@@ -1997,13 +1997,15 @@
* strategy.</p>
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
- * <p>All intents are supported by all devices, except that:
- * * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * PRIVATE_REPROCESSING or YUV_REPROCESSING.
- * * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MANUAL_SENSOR.
- * * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MOTION_TRACKING.</p>
+ * <p>All intents are supported by all devices, except that:</p>
+ * <ul>
+ * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li>
+ * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MANUAL_SENSOR.</li>
+ * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MOTION_TRACKING.</li>
+ * </ul>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -2929,7 +2931,7 @@
* and keep jpeg and thumbnail image data unrotated.</li>
* <li>Rotate the jpeg and thumbnail image data and not set
* {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this
- * case, LIMITED or FULL hardware level devices will report rotated thumnail size in
+ * case, LIMITED or FULL hardware level devices will report rotated thumbnail size in
* capture result, so the width and height will be interchanged if 90 or 270 degree
* orientation is requested. LEGACY device will always report unrotated thumbnail
* size.</li>
@@ -3149,7 +3151,7 @@
* <p>When the state is STATIONARY, the lens parameters are not changing. This could be
* either because the parameters are all fixed, or because the lens has had enough
* time to reach the most recently-requested values.
- * If all these lens parameters are not changable for a camera device, as listed below:</p>
+ * If all these lens parameters are not changeable for a camera device, as listed below:</p>
* <ul>
* <li>Fixed focus (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} == 0</code>), which means
* {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} parameter will always be 0.</li>
@@ -4009,7 +4011,7 @@
* noise model used here is:</p>
* <p>N(x) = sqrt(Sx + O)</p>
* <p>Where x represents the recorded signal of a CFA channel normalized to
- * the range [0, 1], and S and O are the noise model coeffiecients for
+ * the range [0, 1], and S and O are the noise model coefficients for
* that channel.</p>
* <p>A more detailed description of the noise model can be found in the
* Adobe DNG specification for the NoiseProfile tag.</p>
@@ -4054,7 +4056,7 @@
* <li>1.20 <= R >= 1.03 will require some software
* correction to avoid demosaic errors (3-20% divergence).</li>
* <li>R > 1.20 will require strong software correction to produce
- * a usuable image (>20% divergence).</li>
+ * a usable image (>20% divergence).</li>
* </ul>
* <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if
* the camera device has RAW capability.</p>
@@ -4274,7 +4276,7 @@
/**
* <p>Whether <code>RAW</code> images requested have their bayer pattern as described by
* {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
- * <p>This key will only be present in devices advertisting the
+ * <p>This key will only be present in devices advertising the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
* capability which also advertise <code>REMOSAIC_REPROCESSING</code> capability. On all other devices
* RAW targets will have a regular bayer pattern.</p>
@@ -5128,9 +5130,11 @@
/**
* <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* GAMMA_VALUE</p>
- * <p>The tonemap curve will be defined the following formula:
- * * OUT = pow(IN, 1.0 / gamma)
- * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * <p>The tonemap curve will be defined the following formula:</p>
+ * <ul>
+ * <li>OUT = pow(IN, 1.0 / gamma)</li>
+ * </ul>
+ * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
* pow is the power function and gamma is the gamma value specified by this
* key.</p>
* <p>The same curve will be applied to all color channels. The camera device
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1263da6..4d0ba63 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -72,7 +72,7 @@
import java.util.concurrent.Executor;
public final class CameraExtensionSessionImpl extends CameraExtensionSession {
- private static final int PREVIEW_QUEUE_SIZE = 3;
+ private static final int PREVIEW_QUEUE_SIZE = 10;
private static final String TAG = "CameraExtensionSessionImpl";
private final Executor mExecutor;
@@ -1057,15 +1057,8 @@
mClientRequest));
if (mCaptureResultHandler != null) {
- CameraMetadataNative captureResults = new CameraMetadataNative();
- for (CaptureResult.Key key : mSupportedResultKeys) {
- Object value = result.get(key);
- if (value != null) {
- captureResults.set(key, value);
- }
- }
mCaptureResultHandler.onCaptureCompleted(timestamp,
- captureResults);
+ initializeFilteredResults(result));
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1127,6 +1120,11 @@
private class ImageCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) {
+ notifyCaptureFailed();
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (mCaptureFailed) {
img.close();
@@ -1160,6 +1158,9 @@
private class ImageLoopbackCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) { }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
img.close();
}
@@ -1221,7 +1222,8 @@
}
private interface OnImageAvailableListener {
- public void onImageAvailable (ImageReader reader, Image img);
+ void onImageDropped(long timestamp);
+ void onImageAvailable (ImageReader reader, Image img);
}
private class CameraOutputImageCallback implements ImageReader.OnImageAvailableListener,
@@ -1263,6 +1265,29 @@
} else {
mImageListenerMap.put(img.getTimestamp(), new Pair<>(img, null));
}
+
+ notifyDroppedImages(timestamp);
+ }
+
+ private void notifyDroppedImages(long timestamp) {
+ Set<Long> timestamps = mImageListenerMap.keySet();
+ ArrayList<Long> removedTs = new ArrayList<>();
+ for (long ts : timestamps) {
+ if (ts < timestamp) {
+ Log.e(TAG, "Dropped image with ts: " + ts);
+ Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
+ if (entry.second != null) {
+ entry.second.onImageDropped(ts);
+ }
+ if (entry.first != null) {
+ entry.first.close();
+ }
+ removedTs.add(ts);
+ }
+ }
+ for (long ts : removedTs) {
+ mImageListenerMap.remove(ts);
+ }
}
public void registerListener(Long timestamp, OnImageAvailableListener listener) {
@@ -1291,6 +1316,12 @@
entry.first.close();
}
}
+ for (long timestamp : mImageListenerMap.keySet()) {
+ Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(timestamp);
+ if (entry.second != null) {
+ entry.second.onImageDropped(timestamp);
+ }
+ }
mImageListenerMap.clear();
}
}
@@ -1447,7 +1478,6 @@
} else {
notifyConfigurationFailure();
}
-
}
@Override
@@ -1536,7 +1566,16 @@
} else if (mPreviewProcessorType ==
IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
int idx = mPendingResultMap.indexOfKey(timestamp);
- if (idx >= 0) {
+
+ if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) {
+ // Image was dropped before we can receive the capture results
+ if ((mCaptureResultHandler != null)) {
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+ }
+ discardPendingRepeatingResults(idx, mPendingResultMap, false);
+ } else if (idx >= 0) {
+ // Image came before the capture results
ParcelImage parcelImage = initializeParcelImage(
mPendingResultMap.get(timestamp).first);
try {
@@ -1563,6 +1602,7 @@
}
discardPendingRepeatingResults(idx, mPendingResultMap, false);
} else {
+ // Image not yet available
notifyClient = false;
mPendingResultMap.put(timestamp,
new Pair<>(null,
@@ -1581,16 +1621,8 @@
mClientRequest));
if ((mCaptureResultHandler != null) && (mPreviewProcessorType !=
IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR)) {
- CameraMetadataNative captureResults =
- new CameraMetadataNative();
- for (CaptureResult.Key key : mSupportedResultKeys) {
- Object value = result.get(key);
- if (value != null) {
- captureResults.set(key, value);
- }
- }
mCaptureResultHandler.onCaptureCompleted(timestamp,
- captureResults);
+ initializeFilteredResults(result));
}
} else {
mExecutor.execute(
@@ -1657,19 +1689,24 @@
for (int i = idx; i >= 0; i--) {
if (previewMap.valueAt(i).first != null) {
previewMap.valueAt(i).first.close();
- } else {
- if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) {
- Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
- final long ident = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(
- () -> mCallbacks
- .onCaptureFailed(CameraExtensionSessionImpl.this,
- mClientRequest));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ } else if (mClientNotificationsEnabled && (previewMap.valueAt(i).second != null) &&
+ ((i != idx) || notifyCurrentIndex)) {
+ TotalCaptureResult result = previewMap.valueAt(i).second;
+ Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+
+ Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureFailed(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+
}
previewMap.removeAt(i);
}
@@ -1683,6 +1720,12 @@
}
@Override
+ public void onImageDropped(long timestamp) {
+ discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+ mPendingResultMap, true);
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (img == null) {
Log.e(TAG, "Invalid image!");
@@ -1703,6 +1746,15 @@
private class ImageProcessCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) {
+ discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+ mPendingResultMap, true);
+ // Add an empty frame&results entry to flag that we dropped a frame
+ // and valid capture results can immediately return to client.
+ mPendingResultMap.put(timestamp, new Pair<>(null, null));
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (mPendingResultMap.size() + 1 >= PREVIEW_QUEUE_SIZE) {
// We reached the maximum acquired images limit. This is possible in case we
@@ -1768,6 +1820,17 @@
}
}
+ private CameraMetadataNative initializeFilteredResults(TotalCaptureResult result) {
+ CameraMetadataNative captureResults = new CameraMetadataNative();
+ for (CaptureResult.Key key : mSupportedResultKeys) {
+ Object value = result.get(key);
+ if (value != null) {
+ captureResults.set(key, value);
+ }
+ }
+ return captureResults;
+ }
+
private static Size findSmallestAspectMatchedSize(@NonNull List<Size> sizes,
@NonNull Size arSize) {
final float TOLL = .01f;
diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
index 14ed689..34c8336 100644
--- a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
+++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
@@ -277,7 +277,7 @@
* profile.</p>
*
* @return non-modifiable set of dynamic range profiles
- * @throws IllegalArgumentException - If the profile argument is not
+ * @throws IllegalArgumentException If the profile argument is not
* within the list returned by
* getSupportedProfiles()
*
@@ -303,7 +303,7 @@
*
* @return true if the given profile is not suitable for latency sensitive use cases, false
* otherwise
- * @throws IllegalArgumentException - If the profile argument is not
+ * @throws IllegalArgumentException If the profile argument is not
* within the list returned by
* getSupportedProfiles()
*
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 4bf9a74..f701ec3 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -39,6 +39,7 @@
* See {@link FingerprintSensorProperties.SensorType}.
*/
public final @FingerprintSensorProperties.SensorType int sensorType;
+ public final boolean halControlsIllumination;
private final List<SensorLocationInternal> mSensorLocations;
@@ -46,6 +47,7 @@
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
@NonNull List<ComponentInfoInternal> componentInfo,
@FingerprintSensorProperties.SensorType int sensorType,
+ boolean halControlsIllumination,
boolean resetLockoutRequiresHardwareAuthToken,
@NonNull List<SensorLocationInternal> sensorLocations) {
// IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not
@@ -55,6 +57,7 @@
super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
this.sensorType = sensorType;
+ this.halControlsIllumination = halControlsIllumination;
this.mSensorLocations = List.copyOf(sensorLocations);
}
@@ -68,14 +71,15 @@
boolean resetLockoutRequiresHardwareAuthToken) {
// TODO(b/179175438): Value should be provided from the HAL
this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType,
- resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal(
- "" /* displayId */, 540 /* sensorLocationX */, 1636 /* sensorLocationY */,
- 130 /* sensorRadius */)));
+ false /* halControlsIllumination */, resetLockoutRequiresHardwareAuthToken,
+ List.of(new SensorLocationInternal("" /* displayId */, 540 /* sensorLocationX */,
+ 1636 /* sensorLocationY */, 130 /* sensorRadius */)));
}
protected FingerprintSensorPropertiesInternal(Parcel in) {
super(in);
sensorType = in.readInt();
+ halControlsIllumination = in.readBoolean();
mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR);
}
@@ -101,6 +105,7 @@
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(sensorType);
+ dest.writeBoolean(halControlsIllumination);
dest.writeTypedList(mSensorLocations);
}
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index b37c27c..fc6bc55 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -75,8 +75,15 @@
/**
* Sets the display id that the MouseCursorController will be forced to target. Pass
* {@link android.view.Display#INVALID_DISPLAY} to clear the override.
+ *
+ * Note: This method generally blocks until the pointer display override has propagated.
+ * When setting a new override, the caller should ensure that an input device that can control
+ * the mouse pointer is connected. If a new override is set when no such input device is
+ * connected, the caller may be blocked for an arbitrary period of time.
+ *
+ * @return true if the pointer displayId was set successfully, or false if it fails.
*/
- public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId);
+ public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
/**
* Gets the display id that the MouseCursorController is being forced to target. Returns
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index ecdc803..022d213 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -1361,6 +1361,18 @@
return false;
}
+ // Apps with PROPERTY_NO_APP_DATA_STORAGE should not be allowed in scoped storage
+ final String packageName = AppGlobals.getInitialPackage();
+ try {
+ final PackageManager.Property noAppStorageProp = packageManager.getProperty(
+ PackageManager.PROPERTY_NO_APP_DATA_STORAGE, packageName);
+ if (noAppStorageProp != null && noAppStorageProp.getBoolean()) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException ignore) {
+ // Property not defined for the package
+ }
+
boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE);
boolean forceEnableScopedStorage = Compatibility.isChangeEnabled(
FORCE_ENABLE_SCOPED_STORAGE);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a64e63e..196f2f9 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2179,7 +2179,10 @@
}
} else {
UserInfo userInfo = getUserInfo(mUserId);
- return userInfo == null ? "" : userInfo.name;
+ if (userInfo != null && userInfo.name != null) {
+ return userInfo.name;
+ }
+ return "";
}
}
diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
index 29b4570..67a930a 100644
--- a/core/java/android/os/logcat/ILogcatManagerService.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -42,31 +42,4 @@
* @param fd The FD (Socket) of client who makes the request.
*/
void finishThread(in int uid, in int gid, in int pid, in int fd);
-
-
- /**
- * The function is called by UX component to notify
- * LogcatManagerService that the user approved
- * the privileged log data access.
- *
- * @param uid The UID of client who makes the request.
- * @param gid The GID of client who makes the request.
- * @param pid The PID of client who makes the request.
- * @param fd The FD (Socket) of client who makes the request.
- */
- void approve(in int uid, in int gid, in int pid, in int fd);
-
-
- /**
- * The function is called by UX component to notify
- * LogcatManagerService that the user declined
- * the privileged log data access.
- *
- * @param uid The UID of client who makes the request.
- * @param gid The GID of client who makes the request.
- * @param pid The PID of client who makes the request.
- * @param fd The FD (Socket) of client who makes the request.
- */
- void decline(in int uid, in int gid, in int pid, in int fd);
}
-
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0e5a65c..77c0067 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -1292,7 +1292,8 @@
USER_MISSED_LOW_RING_VOLUME,
USER_MISSED_NO_VIBRATE,
USER_MISSED_CALL_SCREENING_SERVICE_SILENCED,
- USER_MISSED_CALL_FILTERS_TIMEOUT
+ USER_MISSED_CALL_FILTERS_TIMEOUT,
+ USER_MISSED_NEVER_RANG
})
@Retention(RetentionPolicy.SOURCE)
public @interface MissedReason {}
@@ -1383,6 +1384,13 @@
public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 1 << 22;
/**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * the call ended before ringing.
+ * @hide
+ */
+ public static final long USER_MISSED_NEVER_RANG = 1 << 23;
+
+ /**
* Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE},
* indicates factors which may have lead the user to miss the call.
* <P>Type: INTEGER</P>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1ef1ac5..dac54cf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7232,13 +7232,6 @@
*/
public static final String LOCATION_SHOW_SYSTEM_OPS = "locationShowSystemOps";
-
- /**
- * Whether or not an indicator experiment has started.
- * @hide
- */
- public static final String LOCATION_INDICATOR_EXPERIMENT_STARTED =
- "locationIndicatorExperimentStarted";
/**
* A flag containing settings used for biometric weak
* @hide
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 425dbb9..0ec95c6 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -904,7 +904,6 @@
// based on its default wallpaper color hints.
mShouldDim = dimAmount != 0f || mShouldDimByDefault;
updateSurfaceDimming();
- updateSurface(false, false, true);
}
private void updateSurfaceDimming() {
@@ -941,6 +940,7 @@
} else {
Log.v(TAG, "Setting wallpaper dimming: " + 0);
surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
+ updateSurface(false, false, true);
}
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 3be4c3ed..24ded93 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -50,12 +50,21 @@
public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub";
/** @hide */
public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen";
+
/**
* Support per app's language selection
* @hide
*/
public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
+ /**
+ * Support locale opt-out and opt-in switch for per app's language.
+ * @hide
+ */
+ public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED =
+ "settings_app_locale_opt_in_enabled";
+
+
/** @hide */
public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
"settings_enable_monitor_phantom_procs";
@@ -97,6 +106,7 @@
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
DEFAULT_FLAGS.put("settings_search_always_expand", "true");
DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
+ DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
@@ -106,6 +116,7 @@
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
+ PERSISTENT_FLAGS.add(SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index fd557e7..2e48c2b 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -719,9 +719,14 @@
private void releaseSurfaces(boolean releaseSurfacePackage) {
mSurfaceAlpha = 1f;
-
- synchronized (mSurfaceControlLock) {
+
+ mSurfaceLock.lock();
+ try {
mSurface.destroy();
+ } finally {
+ mSurfaceLock.unlock();
+ }
+ synchronized (mSurfaceControlLock) {
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
@@ -770,105 +775,99 @@
Transaction surfaceUpdateTransaction) {
boolean realSizeChanged = false;
- mSurfaceLock.lock();
- try {
- mDrawingStopped = !mVisible;
+ mDrawingStopped = !mVisible;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Cur surface: " + mSurface);
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
- // If we are creating the surface control or the parent surface has not
- // changed, then set relative z. Otherwise allow the parent
- // SurfaceChangedCallback to update the relative z. This is needed so that
- // we do not change the relative z before the server is ready to swap the
- // parent surface.
- if (creating) {
- updateRelativeZ(surfaceUpdateTransaction);
- if (mSurfacePackage != null) {
- reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
- }
+ // If we are creating the surface control or the parent surface has not
+ // changed, then set relative z. Otherwise allow the parent
+ // SurfaceChangedCallback to update the relative z. This is needed so that
+ // we do not change the relative z before the server is ready to swap the
+ // parent surface.
+ if (creating) {
+ updateRelativeZ(surfaceUpdateTransaction);
+ if (mSurfacePackage != null) {
+ reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
}
- mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
-
- if (mViewVisibility) {
- surfaceUpdateTransaction.show(mSurfaceControl);
- } else {
- surfaceUpdateTransaction.hide(mSurfaceControl);
- }
-
-
-
- updateBackgroundVisibility(surfaceUpdateTransaction);
- updateBackgroundColor(surfaceUpdateTransaction);
- if (mUseAlpha) {
- float alpha = getFixedAlpha();
- surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
- mSurfaceAlpha = alpha;
- }
-
- surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
- if ((sizeChanged || hintChanged) && !creating) {
- setBufferSize(surfaceUpdateTransaction);
- }
- if (sizeChanged || creating || !isHardwareAccelerated()) {
-
- // Set a window crop when creating the surface or changing its size to
- // crop the buffer to the surface size since the buffer producer may
- // use SCALING_MODE_SCALE and submit a larger size than the surface
- // size.
- if (mClipSurfaceToBounds && mClipBounds != null) {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
- } else {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
- }
-
- surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
-
- if (isHardwareAccelerated()) {
- // This will consume the passed in transaction and the transaction will be
- // applied on a render worker thread.
- replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
- } else {
- onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
- mScreenRect.left /*positionLeft*/,
- mScreenRect.top /*positionTop*/,
- mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
- mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
- }
- if (DEBUG_POSITION) {
- Log.d(TAG, String.format(
- "%d performSurfaceTransaction %s "
- + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
- System.identityHashCode(this),
- isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
- mScreenRect.left, mScreenRect.top, mScreenRect.right,
- mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
- }
- }
- applyTransactionOnVriDraw(surfaceUpdateTransaction);
- updateEmbeddedAccessibilityMatrix(false);
-
- mSurfaceFrame.left = 0;
- mSurfaceFrame.top = 0;
- if (translator == null) {
- mSurfaceFrame.right = mSurfaceWidth;
- mSurfaceFrame.bottom = mSurfaceHeight;
- } else {
- float appInvertedScale = translator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
- }
- final int surfaceWidth = mSurfaceFrame.right;
- final int surfaceHeight = mSurfaceFrame.bottom;
- realSizeChanged = mLastSurfaceWidth != surfaceWidth
- || mLastSurfaceHeight != surfaceHeight;
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- } finally {
- mSurfaceLock.unlock();
}
+ mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
+
+ if (mViewVisibility) {
+ surfaceUpdateTransaction.show(mSurfaceControl);
+ } else {
+ surfaceUpdateTransaction.hide(mSurfaceControl);
+ }
+
+
+
+ updateBackgroundVisibility(surfaceUpdateTransaction);
+ updateBackgroundColor(surfaceUpdateTransaction);
+ if (mUseAlpha) {
+ float alpha = getFixedAlpha();
+ surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+ mSurfaceAlpha = alpha;
+ }
+
+ surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+ if ((sizeChanged || hintChanged) && !creating) {
+ setBufferSize(surfaceUpdateTransaction);
+ }
+ if (sizeChanged || creating || !isHardwareAccelerated()) {
+ // Set a window crop when creating the surface or changing its size to
+ // crop the buffer to the surface size since the buffer producer may
+ // use SCALING_MODE_SCALE and submit a larger size than the surface
+ // size.
+ if (mClipSurfaceToBounds && mClipBounds != null) {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ } else {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+ }
+
+ surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+
+ if (isHardwareAccelerated()) {
+ // This will consume the passed in transaction and the transaction will be
+ // applied on a render worker thread.
+ replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
+ } else {
+ onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
+ mScreenRect.left /*positionLeft*/,
+ mScreenRect.top /*positionTop*/,
+ mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+ mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+ }
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format(
+ "%d performSurfaceTransaction %s "
+ + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+ System.identityHashCode(this),
+ isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
+ mScreenRect.left, mScreenRect.top, mScreenRect.right,
+ mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
+ }
+ }
+ applyTransactionOnVriDraw(surfaceUpdateTransaction);
+ updateEmbeddedAccessibilityMatrix(false);
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (translator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
+ } else {
+ float appInvertedScale = translator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+ }
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+
return realSizeChanged;
}
@@ -1103,21 +1102,30 @@
* Surface for compatibility reasons.
*/
private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
- if (surfaceControlCreated) {
- mSurface.copyFrom(mBlastBufferQueue);
- }
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ boolean needsWorkaround = bufferSizeChanged &&
+ getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
+ if (!surfaceControlCreated && !needsWorkaround) {
+ return;
+ }
+ mSurfaceLock.lock();
+ try {
+ if (surfaceControlCreated) {
+ mSurface.copyFrom(mBlastBufferQueue);
+ }
- if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
- < Build.VERSION_CODES.O) {
- // Some legacy applications use the underlying native {@link Surface} object
- // as a key to whether anything has changed. In these cases, updates to the
- // existing {@link Surface} will be ignored when the size changes.
- // Therefore, we must explicitly recreate the {@link Surface} in these
- // cases.
- if (mBlastBufferQueue != null) {
- mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
- }
- }
+ if (needsWorkaround) {
+ if (mBlastBufferQueue != null) {
+ mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
+ }
+ }
+ } finally {
+ mSurfaceLock.unlock();
+ }
}
private void setBufferSize(Transaction transaction) {
@@ -1200,8 +1208,10 @@
}
mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
+
mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
+ mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback);
}
private void onDrawFinished() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6fc16c6..2475e2c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8206,24 +8206,25 @@
if (canNotifyAutofillEnterExitEvent()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
- if (enter && isFocused()) {
+ if (enter) {
// We have not been laid out yet, hence cannot evaluate
// whether this view is visible to the user, we will do
// the evaluation once layout is complete.
if (!isLaidOut()) {
mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
} else if (isVisibleToUser()) {
- // TODO This is a potential problem that View gets focus before it's visible
- // to User. Ideally View should handle the event when isVisibleToUser()
- // becomes true where it should issue notifyViewEntered().
- afm.notifyViewEntered(this);
- } else {
- afm.enableFillRequestActivityStarted(this);
+ if (isFocused()) {
+ // TODO This is a potential problem that View gets focus before it's
+ // visible to User. Ideally View should handle the event when
+ // isVisibleToUser() becomes true where it should issue
+ // notifyViewEntered().
+ afm.notifyViewEntered(this);
+ } else {
+ afm.notifyViewEnteredForFillDialog(this);
+ }
}
- } else if (!enter && !isFocused()) {
+ } else if (!isFocused()) {
afm.notifyViewExited(this);
- } else if (enter) {
- afm.enableFillRequestActivityStarted(this);
}
}
}
@@ -11752,6 +11753,7 @@
&& (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
if (info.mPositionUpdateListener != null) {
mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
+ info.mPositionUpdateListener = null;
info.mPositionChangedUpdate = null;
}
} else {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 335cd27..0bdbfbc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -590,7 +590,6 @@
@Nullable
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
boolean mPerformContentCapture;
- boolean mPerformAutoFill;
boolean mReportNextDraw;
@@ -858,6 +857,28 @@
*/
private Bundle mRelayoutBundle = new Bundle();
+ private static volatile boolean sAnrReported = false;
+ static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback =
+ new BLASTBufferQueue.TransactionHangCallback() {
+ @Override
+ public void onTransactionHang(boolean isGPUHang) {
+ if (isGPUHang && !sAnrReported) {
+ sAnrReported = true;
+ try {
+ ActivityManager.getService().appNotResponding(
+ "Buffer processing hung up due to stuck fence. Indicates GPU hang");
+ } catch (RemoteException e) {
+ // We asked the system to crash us, but the system
+ // already crashed. Unfortunately things may be
+ // out of control.
+ }
+ } else {
+ // TODO: Do something with this later. For now we just ANR
+ // in dequeue buffer later like we always have.
+ }
+ }
+ };
+
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
@@ -890,7 +911,6 @@
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mPerformContentCapture = true; // also true for the first time the view is added
- mPerformAutoFill = true;
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
@@ -2100,6 +2120,7 @@
}
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
+ mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
Surface blastSurface = mBlastBufferQueue.createSurface();
// Only call transferFrom if the surface has changed to prevent inc the generation ID and
// causing EGL resources to be recreated.
@@ -4196,26 +4217,6 @@
});
}
- @Nullable
- private void registerFrameDrawingCallbackForBlur() {
- if (!isHardwareEnabled()) {
- return;
- }
- final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
- final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
-
- if (!needsCallbackForBlur) {
- return;
- }
-
- final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
- mBlurRegionAggregator.getBlurRegionsCopyForRT();
-
- // The callback will run on the render thread.
- registerRtFrameCallback((frame) -> mBlurRegionAggregator
- .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates));
- }
-
private void registerCallbackForPendingTransactions() {
registerRtFrameCallback(new FrameDrawingCallback() {
@Override
@@ -4253,7 +4254,6 @@
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
- registerFrameDrawingCallbackForBlur();
addFrameCommitCallbackIfNeeded();
boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null;
@@ -4329,18 +4329,6 @@
if (mPerformContentCapture) {
performContentCaptureInitialReport();
}
-
- if (mPerformAutoFill) {
- notifyEnterForAutoFillIfNeeded();
- }
- }
-
- private void notifyEnterForAutoFillIfNeeded() {
- mPerformAutoFill = false;
- final AutofillManager afm = getAutofillManager();
- if (afm != null) {
- afm.notifyViewEnteredForActivityStarted(mView);
- }
}
/**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 0a75992..dcedb30 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -102,6 +102,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import sun.misc.Cleaner;
@@ -645,16 +646,6 @@
private boolean mEnabledForAugmentedAutofillOnly;
/**
- * Indicates whether there are any fields that need to do a fill request
- * after the activity starts.
- *
- * Note: This field will be set to true multiple times if there are many
- * autofillable views. So needs to check mIsFillRequested at the same time to
- * avoid re-trigger autofill.
- */
- private boolean mRequireAutofill;
-
- /**
* Indicates whether there is already a field to do a fill request after
* the activity started.
*
@@ -663,7 +654,7 @@
* triggered autofill, it is unnecessary to trigger again through
* AutofillManager#notifyViewEnteredForActivityStarted.
*/
- private boolean mIsFillRequested;
+ private AtomicBoolean mIsFillRequested;
@Nullable private List<AutofillId> mFillDialogTriggerIds;
@@ -811,8 +802,7 @@
mContext = Objects.requireNonNull(context, "context cannot be null");
mService = service;
mOptions = context.getAutofillOptions();
- mIsFillRequested = false;
- mRequireAutofill = false;
+ mIsFillRequested = new AtomicBoolean(false);
mIsFillDialogEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
@@ -1113,22 +1103,31 @@
}
/**
- * The view have the allowed autofill hints, marked to perform a fill request after layout if
- * the field does not trigger a fill request.
+ * The {@link #DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or the view have
+ * the allowed autofill hints, performs a fill request to know there is any field supported
+ * fill dialog.
*
* @hide
*/
- public void enableFillRequestActivityStarted(View v) {
- if (mRequireAutofill) {
+ public void notifyViewEnteredForFillDialog(View v) {
+ // Skip if the fill request has been performed for a view.
+ if (mIsFillRequested.get()) {
return;
}
if (mIsFillDialogEnabled
|| ArrayUtils.containsAny(v.getAutofillHints(), mFillDialogEnabledHints)) {
if (sDebug) {
- Log.d(TAG, "Trigger fill request at starting");
+ Log.d(TAG, "Trigger fill request at view entered");
}
- mRequireAutofill = true;
+
+ // Note: No need for atomic getAndSet as this method is called on the UI thread.
+ mIsFillRequested.set(true);
+
+ int flags = FLAG_SUPPORTS_FILL_DIALOG;
+ flags |= FLAG_VIEW_NOT_FOCUSED;
+ // use root view, so autofill UI does not trigger immediately.
+ notifyViewEntered(v.getRootView(), flags);
}
}
@@ -1136,25 +1135,6 @@
return mIsFillDialogEnabled || !ArrayUtils.isEmpty(mFillDialogEnabledHints);
}
- /**
- * Notify autofill to do a fill request while the activity started.
- *
- * @hide
- */
- public void notifyViewEnteredForActivityStarted(@NonNull View view) {
- if (!hasAutofillFeature() || !hasFillDialogUiFeature()) {
- return;
- }
-
- if (!mRequireAutofill || mIsFillRequested) {
- return;
- }
-
- int flags = FLAG_SUPPORTS_FILL_DIALOG;
- flags |= FLAG_VIEW_NOT_FOCUSED;
- notifyViewEntered(view, flags);
- }
-
private int getImeStateFlag(View v) {
final WindowInsets rootWindowInsets = v.getRootWindowInsets();
if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
@@ -1203,7 +1183,7 @@
}
AutofillCallback callback;
synchronized (mLock) {
- mIsFillRequested = true;
+ mIsFillRequested.set(true);
callback = notifyViewEnteredLocked(view, flags);
}
@@ -2119,8 +2099,7 @@
mFillableIds = null;
mSaveTriggerId = null;
mIdShownFillUi = null;
- mIsFillRequested = false;
- mRequireAutofill = false;
+ mIsFillRequested.set(false);
mShowAutofillDialogCalled = false;
mFillDialogTriggerIds = null;
if (resetEnteredIds) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f0a685e..3fee914 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -245,7 +245,7 @@
SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
- private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 250;
+ private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125;
@VisibleForTesting
int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
diff --git a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
index 402d7fe..4e1ecc2 100644
--- a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
+++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.util.LongSparseArray;
import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -232,9 +233,12 @@
private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet();
@GuardedBy("mRtLock")
private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray();
+ private long mLastFrameNumber = 0;
+ private BlurRegion[] mLastFrameBlurRegions = null;
private final ViewRootImpl mViewRoot;
private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0];
private boolean mHasUiUpdates;
+ private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener;
public Aggregator(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
@@ -277,6 +281,38 @@
Log.d(TAG, "Remove " + drawable);
}
}
+
+ if (mOnPreDrawListener == null && mViewRoot.getView() != null
+ && hasRegions()) {
+ registerPreDrawListener();
+ }
+ }
+
+ private void registerPreDrawListener() {
+ mOnPreDrawListener = () -> {
+ final boolean hasUiUpdates = hasUpdates();
+
+ if (hasUiUpdates || hasRegions()) {
+ final BlurRegion[] blurRegionsForNextFrame = getBlurRegionsCopyForRT();
+
+ mViewRoot.registerRtFrameCallback(frame -> {
+ synchronized (mRtLock) {
+ mLastFrameNumber = frame;
+ mLastFrameBlurRegions = blurRegionsForNextFrame;
+ handleDispatchBlurTransactionLocked(
+ frame, blurRegionsForNextFrame, hasUiUpdates);
+ }
+ });
+ }
+ if (!hasRegions() && mViewRoot.getView() != null) {
+ mViewRoot.getView().getViewTreeObserver()
+ .removeOnPreDrawListener(mOnPreDrawListener);
+ mOnPreDrawListener = null;
+ }
+ return true;
+ };
+
+ mViewRoot.getView().getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
}
// Called from a thread pool
@@ -290,7 +326,14 @@
mFrameRtUpdates.put(frameNumber, frameRtUpdates);
}
frameRtUpdates.add(update);
+
+ if (mLastFrameNumber == frameNumber) {
+ // The transaction for this frame has already been sent, so we have to manually
+ // trigger sending a transaction here in order to apply this position update
+ handleDispatchBlurTransactionLocked(frameNumber, mLastFrameBlurRegions, true);
+ }
}
+
}
/**
@@ -329,29 +372,27 @@
/**
* Called on RenderThread.
*
- * @return all blur regions if there are any ui or position updates for this frame,
- * null otherwise
+ * @return true if it is necessary to send an update to Sf this frame
*/
+ @GuardedBy("mRtLock")
@VisibleForTesting
- public float[][] getBlurRegionsToDispatchToSf(long frameNumber,
- BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
- synchronized (mRtLock) {
- if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0
- || mFrameRtUpdates.keyAt(0) > frameNumber)) {
- return null;
- }
+ public float[][] getBlurRegionsForFrameLocked(long frameNumber,
+ BlurRegion[] blurRegionsForFrame, boolean forceUpdate) {
+ if (!forceUpdate && (mFrameRtUpdates.size() == 0
+ || mFrameRtUpdates.keyAt(0) > frameNumber)) {
+ return null;
+ }
- // mFrameRtUpdates holds position updates coming from a thread pool span from
- // RenderThread. At this point, all position updates for frame frameNumber should
- // have been added to mFrameRtUpdates.
- // Here, we apply all updates for frames <= frameNumber in case some previous update
- // has been missed. This also protects mFrameRtUpdates from memory leaks.
- while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
- final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
- mFrameRtUpdates.removeAt(0);
- for (int i = 0; i < frameUpdates.size(); i++) {
- frameUpdates.valueAt(i).run();
- }
+ // mFrameRtUpdates holds position updates coming from a thread pool span from
+ // RenderThread. At this point, all position updates for frame frameNumber should
+ // have been added to mFrameRtUpdates.
+ // Here, we apply all updates for frames <= frameNumber in case some previous update
+ // has been missed. This also protects mFrameRtUpdates from memory leaks.
+ while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
+ final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
+ mFrameRtUpdates.removeAt(0);
+ for (int i = 0; i < frameUpdates.size(); i++) {
+ frameUpdates.valueAt(i).run();
}
}
@@ -370,13 +411,13 @@
}
/**
- * Called on RenderThread in FrameDrawingCallback.
- * Dispatch all blur regions if there are any ui or position updates.
+ * Dispatch all blur regions if there are any ui or position updates for that frame.
*/
- public void dispatchBlurTransactionIfNeeded(long frameNumber,
- BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
- final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber,
- blurRegionsForFrame, hasUiUpdatesForFrame);
+ @GuardedBy("mRtLock")
+ private void handleDispatchBlurTransactionLocked(long frameNumber, BlurRegion[] blurRegions,
+ boolean forceUpdate) {
+ float[][] blurRegionsArray =
+ getBlurRegionsForFrameLocked(frameNumber, blurRegions, forceUpdate);
if (blurRegionsArray != null) {
mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 46b4630..ef8f2db 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -88,7 +88,7 @@
in int notificationLocation, boolean modifiedBeforeSending);
void onNotificationSettingsViewed(String key);
void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
- void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, boolean isBubbleSuppressed);
+ void onBubbleMetadataFlagChanged(String key, int flags);
void hideCurrentInputMethodForBubbles();
void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
oneway void clearInlineReplyUriPermissions(String key);
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index c0f7b41..4af28ea 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -47,6 +47,43 @@
return env;
}
+ struct {
+ jmethodID onTransactionHang;
+} gTransactionHangCallback;
+
+class TransactionHangCallbackWrapper : public LightRefBase<TransactionHangCallbackWrapper> {
+public:
+ explicit TransactionHangCallbackWrapper(JNIEnv* env, jobject jobject) {
+ env->GetJavaVM(&mVm);
+ mTransactionHangObject = env->NewGlobalRef(jobject);
+ LOG_ALWAYS_FATAL_IF(!mTransactionHangObject, "Failed to make global ref");
+ }
+
+ ~TransactionHangCallbackWrapper() {
+ if (mTransactionHangObject) {
+ getenv()->DeleteGlobalRef(mTransactionHangObject);
+ mTransactionHangObject = nullptr;
+ }
+ }
+
+ void onTransactionHang(bool isGpuHang) {
+ if (mTransactionHangObject) {
+ getenv()->CallVoidMethod(mTransactionHangObject,
+ gTransactionHangCallback.onTransactionHang, isGpuHang);
+ }
+ }
+
+private:
+ JavaVM* mVm;
+ jobject mTransactionHangObject;
+
+ JNIEnv* getenv() {
+ JNIEnv* env;
+ mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+ return env;
+ }
+};
+
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName,
jboolean updateDestinationFrame) {
ScopedUtfChars name(env, jName);
@@ -141,6 +178,20 @@
sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl));
}
+
+static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr,
+ jobject transactionHangCallback) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ if (transactionHangCallback == nullptr) {
+ queue->setTransactionHangCallback(nullptr);
+ } else {
+ sp<TransactionHangCallbackWrapper> wrapper =
+ new TransactionHangCallbackWrapper{env, transactionHangCallback};
+ queue->setTransactionHangCallback([wrapper](bool isGpuHang) {
+ wrapper->onTransactionHang(isGpuHang);
+ });
+ }
+}
static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr,
jlong frameNum) {
@@ -163,7 +214,10 @@
{"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
{"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions},
{"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl},
- {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions}
+ {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions},
+ {"nativeSetTransactionHangCallback",
+ "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
+ (void*)nativeSetTransactionHangCallback},
// clang-format on
};
@@ -180,6 +234,11 @@
jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
gTransactionConsumer.accept =
GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
+ jclass transactionHangClass =
+ FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback");
+ gTransactionHangCallback.onTransactionHang =
+ GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V");
+
return 0;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 217166c..0f328b0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6776,9 +6776,8 @@
</activity>
<activity android:name="com.android.server.logcat.LogAccessDialogActivity"
- android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
+ android:theme="@style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
- android:label="@string/log_access_confirmation_title"
android:exported="false">
</activity>
@@ -7057,6 +7056,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.notification.ReviewNotificationPermissionsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 95d2712..c54638a 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -45,3 +45,4 @@
# Telephony
per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
+per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index edaf8cf..689ff66 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5451,6 +5451,8 @@
<bool name="config_supportsHardwareCamToggle">false</bool>
<!-- Whether a camera intent is launched when the lens cover is toggled -->
<bool name="config_launchCameraOnCameraLensCoverToggle">true</bool>
+ <!-- Whether changing sensor privacy SW setting requires device to be unlocked -->
+ <bool name="config_sensorPrivacyRequiresAuthentication">true</bool>
<!-- List containing the allowed install sources for accessibility service. -->
<string-array name="config_accessibility_allowed_install_source" translatable="false"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b882123..443f9a6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4661,6 +4661,7 @@
<java-symbol type="bool" name="config_supportsHardwareMicToggle" />
<java-symbol type="bool" name="config_supportsHardwareCamToggle" />
<java-symbol type="bool" name="config_launchCameraOnCameraLensCoverToggle" />
+ <java-symbol type="bool" name="config_sensorPrivacyRequiresAuthentication" />
<java-symbol type="dimen" name="starting_surface_icon_size" />
<java-symbol type="dimen" name="starting_surface_default_icon_size" />
diff --git a/core/tests/coretests/src/android/view/BlurAggregatorTest.java b/core/tests/coretests/src/android/view/BlurAggregatorTest.java
index b01f275..ded925e 100644
--- a/core/tests/coretests/src/android/view/BlurAggregatorTest.java
+++ b/core/tests/coretests/src/android/view/BlurAggregatorTest.java
@@ -65,7 +65,7 @@
drawable.setBlurRadius(TEST_BLUR_RADIUS);
final boolean hasUpdates = mAggregator.hasUpdates();
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
- mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, hasUpdates);
+ mAggregator.getBlurRegionsForFrameLocked(TEST_FRAME_NUMBER, blurRegions, hasUpdates);
return drawable;
}
@@ -154,7 +154,7 @@
assertEquals(1, blurRegions.length);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
- mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions,
+ mAggregator.getBlurRegionsForFrameLocked(TEST_FRAME_NUMBER, blurRegions,
mAggregator.hasUpdates());
assertEquals(1, blurRegions[0].rect.left);
assertEquals(2, blurRegions[0].rect.top);
@@ -169,7 +169,7 @@
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
- float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNull(blurRegionsForSf);
}
@@ -182,7 +182,7 @@
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
- float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
@@ -197,7 +197,7 @@
assertEquals(1, blurRegions.length);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
- float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
@@ -216,7 +216,7 @@
assertEquals(1, blurRegions.length);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
- float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
@@ -237,19 +237,19 @@
assertEquals(2, blurRegions.length);
// Check that an update in one of the drawables triggers a dispatch of all blur regions
- float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(2, blurRegionsForSf.length);
// Check that the Aggregator deleted all position updates for frame TEST_FRAME_NUMBER
- blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
assertNull(blurRegionsForSf);
// Check that a position update triggers a dispatch of all blur regions
drawable2.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
- blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(2, blurRegionsForSf.length);
@@ -292,7 +292,7 @@
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER + 1, 5, 6, 7, 8);
- final float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ final float[][] blurRegionsForSf = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
@@ -303,7 +303,7 @@
assertEquals(3f, blurRegionsForSf[0][4]);
assertEquals(4f, blurRegionsForSf[0][5]);
- final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsToDispatchToSf(
+ final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsForFrameLocked(
TEST_FRAME_NUMBER + 1, blurRegions, /* hasUiUpdates= */ false);
assertNotNull(blurRegionsForSfForNextFrame);
assertEquals(1, blurRegionsForSfForNextFrame.length);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 891c82d..0e8388b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2485,6 +2485,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "323235828": {
+ "message": "Delaying app transition for recents animation to finish",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"327461496": {
"message": "Complete pause: %s",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 4b723d1..1c41d06 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -43,6 +43,12 @@
private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr);
private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr,
long frameNumber);
+ private static native void nativeSetTransactionHangCallback(long ptr,
+ TransactionHangCallback callback);
+
+ public interface TransactionHangCallback {
+ void onTransactionHang(boolean isGpuHang);
+ }
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@@ -184,4 +190,8 @@
public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) {
return nativeGatherPendingTransactions(mNativeObject, frameNumber);
}
+
+ public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
+ nativeSetTransactionHangCallback(mNativeObject, hangCallback);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 82f8a13..faada1a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -777,22 +777,14 @@
return null;
}
- private void updateCallbackIfNecessary() {
- updateCallbackIfNecessary(true /* deferCallbackUntilAllActivitiesCreated */);
- }
-
/**
* Notifies listeners about changes to split states if necessary.
- *
- * @param deferCallbackUntilAllActivitiesCreated boolean to indicate whether the split info
- * callback should be deferred until all the
- * organized activities have been created.
*/
- private void updateCallbackIfNecessary(boolean deferCallbackUntilAllActivitiesCreated) {
+ private void updateCallbackIfNecessary() {
if (mEmbeddingCallback == null) {
return;
}
- if (deferCallbackUntilAllActivitiesCreated && !allActivitiesCreated()) {
+ if (!allActivitiesCreated()) {
return;
}
List<SplitInfo> currentSplitStates = getActiveSplitStates();
@@ -848,9 +840,7 @@
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
for (TaskFragmentContainer container : containers) {
- if (container.getInfo() == null
- || container.getInfo().getActivities().size()
- != container.collectActivities().size()) {
+ if (!container.taskInfoActivityCountMatchesCreated()) {
return false;
}
}
@@ -1035,11 +1025,8 @@
&& container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
// The onTaskFragmentInfoChanged callback containing this activity has not
// reached the client yet, so add the activity to the pending appeared
- // activities and send a split info callback to the client before
- // {@link Activity#onCreate} is called.
+ // activities.
container.addPendingAppearedActivity(activity);
- updateCallbackIfNecessary(
- false /* deferCallbackUntilAllActivitiesCreated */);
return;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 35981d3..26bbcbb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -145,6 +145,18 @@
return allActivities;
}
+ /**
+ * Checks if the count of activities from the same process in task fragment info corresponds to
+ * the ones created and available on the client side.
+ */
+ boolean taskInfoActivityCountMatchesCreated() {
+ if (mInfo == null) {
+ return false;
+ }
+ return mPendingAppearedActivities.isEmpty()
+ && mInfo.getActivities().size() == collectActivities().size();
+ }
+
ActivityStack toActivityStack() {
return new ActivityStack(collectActivities(), isEmpty());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 227494c..31fc6a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -71,7 +71,7 @@
private long mLastAccessed;
@Nullable
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
/** Whether the bubble should show a dot for the notification indicating updated content. */
private boolean mShowBubbleUpdateDot = true;
@@ -192,13 +192,13 @@
@VisibleForTesting(visibility = PRIVATE)
public Bubble(@NonNull final BubbleEntry entry,
- @Nullable final Bubbles.SuppressionChangedListener listener,
+ @Nullable final Bubbles.BubbleMetadataFlagListener listener,
final Bubbles.PendingIntentCanceledListener intentCancelListener,
Executor mainExecutor) {
mKey = entry.getKey();
mGroupKey = entry.getGroupKey();
mLocusId = entry.getLocusId();
- mSuppressionListener = listener;
+ mBubbleMetadataFlagListener = listener;
mIntentCancelListener = intent -> {
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
@@ -606,8 +606,8 @@
mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
}
- if (showInShade() != prevShowInShade && mSuppressionListener != null) {
- mSuppressionListener.onBubbleNotificationSuppressionChange(this);
+ if (showInShade() != prevShowInShade && mBubbleMetadataFlagListener != null) {
+ mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
}
}
@@ -626,8 +626,8 @@
} else {
mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
}
- if (prevSuppressed != suppressBubble && mSuppressionListener != null) {
- mSuppressionListener.onBubbleNotificationSuppressionChange(this);
+ if (prevSuppressed != suppressBubble && mBubbleMetadataFlagListener != null) {
+ mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
}
}
@@ -771,12 +771,17 @@
return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
}
- void setShouldAutoExpand(boolean shouldAutoExpand) {
+ @VisibleForTesting
+ public void setShouldAutoExpand(boolean shouldAutoExpand) {
+ boolean prevAutoExpand = shouldAutoExpand();
if (shouldAutoExpand) {
enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
} else {
disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
}
+ if (prevAutoExpand != shouldAutoExpand && mBubbleMetadataFlagListener != null) {
+ mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
+ }
}
public void setIsBubble(final boolean isBubble) {
@@ -799,6 +804,10 @@
return (mFlags & option) != 0;
}
+ public int getFlags() {
+ return mFlags;
+ }
+
@Override
public String toString() {
return "Bubble{" + mKey + '}';
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 806c395..f407bdc 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
@@ -323,7 +323,7 @@
public void initialize() {
mBubbleData.setListener(mBubbleDataListener);
- mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged);
+ mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
mBubbleData.setPendingIntentCancelledListener(bubble -> {
if (bubble.getBubbleIntent() == null) {
@@ -554,11 +554,10 @@
}
@VisibleForTesting
- public void onBubbleNotificationSuppressionChanged(Bubble bubble) {
+ public void onBubbleMetadataFlagChanged(Bubble bubble) {
// Make sure NoMan knows suppression state so that anyone querying it can tell.
try {
- mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(),
- !bubble.showInShade(), bubble.isSuppressed());
+ mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
} catch (RemoteException e) {
// Bad things have happened
}
@@ -1038,7 +1037,15 @@
}
} else {
Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
- inflateAndAdd(bubble, suppressFlyout, showInShade);
+ if (notif.shouldSuppressNotificationList()) {
+ // If we're suppressing notifs for DND, we don't want the bubbles to randomly
+ // expand when DND turns off so flip the flag.
+ if (bubble.shouldAutoExpand()) {
+ bubble.setShouldAutoExpand(false);
+ }
+ } else {
+ inflateAndAdd(bubble, suppressFlyout, showInShade);
+ }
}
}
@@ -1070,7 +1077,8 @@
}
}
- private void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
+ @VisibleForTesting
+ public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
// shouldBubbleUp checks canBubble & for bubble metadata
boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
@@ -1096,7 +1104,8 @@
}
}
- private void onRankingUpdated(RankingMap rankingMap,
+ @VisibleForTesting
+ public void onRankingUpdated(RankingMap rankingMap,
HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
if (mTmpRanking == null) {
mTmpRanking = new NotificationListenerService.Ranking();
@@ -1107,19 +1116,22 @@
Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
BubbleEntry entry = entryData.first;
boolean shouldBubbleUp = entryData.second;
-
if (entry != null && !isCurrentProfile(
entry.getStatusBarNotification().getUser().getIdentifier())) {
return;
}
-
+ if (entry != null && (entry.shouldSuppressNotificationList()
+ || entry.getRanking().isSuspended())) {
+ shouldBubbleUp = false;
+ }
rankingMap.getRanking(key, mTmpRanking);
- boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
- if (isActiveBubble && !mTmpRanking.canBubble()) {
+ boolean isActiveOrInOverflow = mBubbleData.hasAnyBubbleWithKey(key);
+ boolean isActive = mBubbleData.hasBubbleInStackWithKey(key);
+ if (isActiveOrInOverflow && !mTmpRanking.canBubble()) {
// If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
// This means that the app or channel's ability to bubble has been revoked.
mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
- } else if (isActiveBubble && (!shouldBubbleUp || entry.getRanking().isSuspended())) {
+ } else if (isActiveOrInOverflow && !shouldBubbleUp) {
// If this entry is allowed to bubble, but cannot currently bubble up or is
// suspended, dismiss it. This happens when DND is enabled and configured to hide
// bubbles, or focus mode is enabled and the app is designated as distracting.
@@ -1127,9 +1139,9 @@
// notification, so that the bubble will be re-created if shouldBubbleUp returns
// true.
mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
- } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
+ } else if (entry != null && mTmpRanking.isBubble() && !isActive) {
entry.setFlagBubble(true);
- onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended());
+ onEntryUpdated(entry, shouldBubbleUp);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index c98c0e6..e4a0fd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -159,7 +159,7 @@
private Listener mListener;
@Nullable
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
private Bubbles.PendingIntentCanceledListener mCancelledListener;
/**
@@ -190,9 +190,8 @@
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
}
- public void setSuppressionChangedListener(
- Bubbles.SuppressionChangedListener listener) {
- mSuppressionListener = listener;
+ public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
+ mBubbleMetadataFlagListener = listener;
}
public void setPendingIntentCancelledListener(
@@ -311,7 +310,7 @@
bubbleToReturn = mPendingBubbles.get(key);
} else if (entry != null) {
// New bubble
- bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener,
+ bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener,
mMainExecutor);
} else {
// Persisted bubble being promoted
@@ -1058,6 +1057,22 @@
return null;
}
+ /**
+ * Get a pending bubble with given notification <code>key</code>
+ *
+ * @param key notification key
+ * @return bubble that matches or null
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public Bubble getPendingBubbleWithKey(String key) {
+ for (Bubble b : mPendingBubbles.values()) {
+ if (b.getKey().equals(key)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
@VisibleForTesting(visibility = PRIVATE)
void setTimeSource(TimeSource timeSource) {
mTimeSource = timeSource;
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 2b2a2f7..c7db8d8 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
@@ -263,10 +263,10 @@
void onBubbleExpandChanged(boolean isExpanding, String key);
}
- /** Listener to be notified when the flags for notification or bubble suppression changes.*/
- interface SuppressionChangedListener {
- /** Called when the notification suppression state of a bubble changes. */
- void onBubbleNotificationSuppressionChange(Bubble bubble);
+ /** Listener to be notified when the flags on BubbleMetadata have changed. */
+ interface BubbleMetadataFlagListener {
+ /** Called when the flags on BubbleMetadata have changed for the provided bubble. */
+ void onBubbleMetadataFlagChanged(Bubble bubble);
}
/** Listener to be notified when a pending intent has been canceled for a bubble. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 4b125b1..6305959 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -24,6 +24,7 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Property;
import android.view.GestureDetector;
@@ -37,6 +38,8 @@
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
@@ -80,7 +83,6 @@
private final Rect mTempRect = new Rect();
private FrameLayout mDividerBar;
-
static final Property<DividerView, Integer> DIVIDER_HEIGHT_PROPERTY =
new Property<DividerView, Integer>(Integer.class, "height") {
@Override
@@ -109,6 +111,74 @@
}
};
+ private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
+ if (isLandscape()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
+ mContext.getString(R.string.accessibility_action_divider_left_full)));
+ if (snapAlgorithm.isFirstSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
+ mContext.getString(R.string.accessibility_action_divider_left_70)));
+ }
+ if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
+ // Only show the middle target if there are more than 1 split target
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
+ mContext.getString(R.string.accessibility_action_divider_left_50)));
+ }
+ if (snapAlgorithm.isLastSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
+ mContext.getString(R.string.accessibility_action_divider_left_30)));
+ }
+ info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
+ mContext.getString(R.string.accessibility_action_divider_right_full)));
+ } else {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
+ mContext.getString(R.string.accessibility_action_divider_top_full)));
+ if (snapAlgorithm.isFirstSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
+ mContext.getString(R.string.accessibility_action_divider_top_70)));
+ }
+ if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
+ // Only show the middle target if there are more than 1 split target
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
+ mContext.getString(R.string.accessibility_action_divider_top_50)));
+ }
+ if (snapAlgorithm.isLastSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
+ mContext.getString(R.string.accessibility_action_divider_top_30)));
+ }
+ info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
+ mContext.getString(R.string.accessibility_action_divider_bottom_full)));
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(@NonNull View host, int action,
+ @Nullable Bundle args) {
+ DividerSnapAlgorithm.SnapTarget nextTarget = null;
+ DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
+ if (action == R.id.action_move_tl_full) {
+ nextTarget = snapAlgorithm.getDismissEndTarget();
+ } else if (action == R.id.action_move_tl_70) {
+ nextTarget = snapAlgorithm.getLastSplitTarget();
+ } else if (action == R.id.action_move_tl_50) {
+ nextTarget = snapAlgorithm.getMiddleTarget();
+ } else if (action == R.id.action_move_tl_30) {
+ nextTarget = snapAlgorithm.getFirstSplitTarget();
+ } else if (action == R.id.action_move_rb_full) {
+ nextTarget = snapAlgorithm.getDismissStartTarget();
+ }
+ if (nextTarget != null) {
+ mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), nextTarget);
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ };
+
public DividerView(@NonNull Context context) {
super(context);
}
@@ -179,6 +249,7 @@
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
setOnTouchListener(this);
+ mHandle.setAccessibilityDelegate(mHandleDelegate);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 72c8141..145e527 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -29,6 +29,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -63,6 +64,7 @@
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
@@ -79,6 +81,7 @@
context,
tvPipBoundsState,
tvPipBoundsAlgorithm,
+ pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
@@ -185,4 +188,12 @@
static PipParamsChangedForwarder providePipParamsChangedForwarder() {
return new PipParamsChangedForwarder();
}
+
+ @WMSingleton
+ @Provides
+ static PipAppOpsListener providePipAppOpsListener(Context context,
+ PipTaskOrganizer pipTaskOrganizer,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipAppOpsListener(context, pipTaskOrganizer::removePip, mainExecutor);
+ }
}
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 3335673..db6131a 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
@@ -78,7 +78,6 @@
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
@@ -435,14 +434,6 @@
return new FloatingContentCoordinator();
}
- @WMSingleton
- @Provides
- static PipAppOpsListener providePipAppOpsListener(Context context,
- PipTouchHandler pipTouchHandler,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
- }
-
// Needs handler for registering broadcast receivers
@WMSingleton
@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 7513e51..1bc9e31 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
@@ -51,6 +51,7 @@
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -63,9 +64,7 @@
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
@@ -211,8 +210,7 @@
@Provides
static Optional<Pip> providePip(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -221,8 +219,8 @@
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
}
@@ -241,12 +239,6 @@
@WMSingleton
@Provides
- static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
- return new PipKeepClearAlgorithm();
- }
-
- @WMSingleton
- @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
@@ -333,6 +325,14 @@
@WMSingleton
@Provides
+ static PipAppOpsListener providePipAppOpsListener(Context context,
+ PipTouchHandler pipTouchHandler,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static PipMotionHelper providePipMotionHelper(Context context,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
index d97d2d6..48a3fc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.pip;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
@@ -28,7 +28,6 @@
import android.util.Pair;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipUtils;
public class PipAppOpsListener {
private static final String TAG = PipAppOpsListener.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 2e8b5b7..42ceb42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -76,6 +76,7 @@
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -109,9 +110,7 @@
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
- private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
- private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
@@ -247,10 +246,6 @@
Set<Rect> unrestricted) {
if (mPipBoundsState.getDisplayId() == displayId) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
- mPipMotionHelper.moveToBounds(mPipKeepClearAlgorithm.adjust(
- mPipBoundsState.getBounds(),
- mPipBoundsState.getRestrictedKeepClearAreas(),
- mPipBoundsState.getUnrestrictedKeepClearAreas()));
}
}
};
@@ -289,8 +284,7 @@
@Nullable
public static Pip create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -305,7 +299,7 @@
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+ pipBoundsState, pipMediaController,
phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
@@ -316,9 +310,7 @@
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
@@ -341,9 +333,7 @@
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
- mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
- mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
deleted file mode 100644
index a83258f..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import android.graphics.Rect;
-
-import java.util.Set;
-
-/**
- * Calculates the adjusted position that does not occlude keep clear areas.
- */
-public class PipKeepClearAlgorithm {
-
- /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
- public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
- Set<Rect> unrestrictedKeepClearAreas) {
- if (restrictedKeepClearAreas.isEmpty()) {
- return defaultBounds;
- }
- // TODO(b/183746978): implement the adjustment algorithm
- // naively check if areas intersect, an if so move PiP upwards
- Rect outBounds = new Rect(defaultBounds);
- for (Rect r : restrictedKeepClearAreas) {
- if (r.intersect(outBounds)) {
- outBounds.offset(0, r.top - outBounds.bottom);
- }
- }
- return outBounds;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index e9b6bab..5a21e07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 8326588..fcd1f95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -45,6 +45,7 @@
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -97,6 +98,7 @@
private final TvPipBoundsState mTvPipBoundsState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+ private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
@@ -121,6 +123,7 @@
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
@@ -136,6 +139,7 @@
context,
tvPipBoundsState,
tvPipBoundsAlgorithm,
+ pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
@@ -153,6 +157,7 @@
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
@@ -181,6 +186,7 @@
mTvPipMenuController = tvPipMenuController;
mTvPipMenuController.setDelegate(this);
+ mAppOpsListener = pipAppOpsListener;
mPipTaskOrganizer = pipTaskOrganizer;
pipTransitionController.registerPipTransitionCallback(this);
@@ -521,6 +527,12 @@
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
checkIfPinnedTaskAppeared();
+ mAppOpsListener.onActivityPinned(packageName);
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ mAppOpsListener.onActivityUnpinned();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 868e456..320c05c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -494,6 +494,14 @@
setFrameHighlighted(false);
}
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (!hasWindowFocus) {
+ hideAllUserControls();
+ }
+ }
+
private void animateAlphaTo(float alpha, View view) {
if (view.getAlpha() == alpha) {
return;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 169f03e..bde94d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -115,7 +115,7 @@
private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
@Mock
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
@Mock
private Bubbles.PendingIntentCanceledListener mPendingIntentCanceledListener;
@@ -136,30 +136,47 @@
mock(NotificationListenerService.Ranking.class);
when(ranking.isTextChanged()).thenReturn(true);
mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
- mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null,
+ mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null,
mMainExecutor);
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null);
- mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null,
+ mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null,
mMainExecutor);
mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null,
new LocusId("locusId1"));
- mBubbleLocusId = new Bubble(mEntryLocusId, mSuppressionListener, null, mMainExecutor);
+ mBubbleLocusId = new Bubble(mEntryLocusId,
+ mBubbleMetadataFlagListener,
+ null /* pendingIntentCanceledListener */,
+ mMainExecutor);
- mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleA1 = new Bubble(mEntryA1,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleA2 = new Bubble(mEntryA2,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleA3 = new Bubble(mEntryA3,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleB1 = new Bubble(mEntryB1,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleB2 = new Bubble(mEntryB2,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleB3 = new Bubble(mEntryB3,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleC1 = new Bubble(mEntryC1,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index 819a984..e8f3f69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -63,7 +63,7 @@
private Bubble mBubble;
@Mock
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
@Before
public void setUp() {
@@ -81,7 +81,7 @@
when(mNotif.getBubbleMetadata()).thenReturn(metadata);
when(mSbn.getKey()).thenReturn("mock");
mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false);
- mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null, mMainExecutor);
+ mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
}
@Test
@@ -144,22 +144,22 @@
}
@Test
- public void testSuppressionListener_change_notified() {
+ public void testBubbleMetadataFlagListener_change_notified() {
assertThat(mBubble.showInShade()).isTrue();
mBubble.setSuppressNotification(true);
assertThat(mBubble.showInShade()).isFalse();
- verify(mSuppressionListener).onBubbleNotificationSuppressionChange(mBubble);
+ verify(mBubbleMetadataFlagListener).onBubbleMetadataFlagChanged(mBubble);
}
@Test
- public void testSuppressionListener_noChange_doesntNotify() {
+ public void testBubbleMetadataFlagListener_noChange_doesntNotify() {
assertThat(mBubble.showInShade()).isTrue();
mBubble.setSuppressNotification(false);
- verify(mSuppressionListener, never()).onBubbleNotificationSuppressionChange(any());
+ verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 5368b7d..df18133 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -45,6 +45,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -75,7 +76,6 @@
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
- @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -100,12 +100,12 @@
return null;
}).when(mMockExecutor).execute(any());
mPipController = new PipController(mContext, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
+ mMockPipBoundsState, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
- mMockExecutor);
+ mMockTaskStackListener, mPipParamsChangedForwarder,
+ mMockOneHandedController, mMockExecutor);
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
}
@@ -133,12 +133,12 @@
when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
assertNull(PipController.create(spyContext, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
+ mMockPipBoundsState, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
- mMockExecutor));
+ mMockTaskStackListener, mPipParamsChangedForwarder,
+ mMockOneHandedController, mMockExecutor));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
deleted file mode 100644
index f657b5e..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-/**
- * Unit tests against {@link PipKeepClearAlgorithm}.
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipKeepClearAlgorithmTest extends ShellTestCase {
-
- private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
-
-
- @Before
- public void setUp() throws Exception {
- mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
- }
-
- @Test
- public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(50, 50, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
- Set.of());
-
- assertFalse(outBounds.contains(keepClearRect));
- }
-
- @Test
- public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(100, 100, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
- Set.of());
-
- assertEquals(inBounds, outBounds);
- }
-
- @Test
- public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
- // TODO(b/183746978): update this test to accommodate for the updated algorithm
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(50, 50, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
- Set.of(keepClearRect));
-
- assertEquals(inBounds, outBounds);
- }
-
- @Test
- public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(100, 100, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
- Set.of(keepClearRect));
-
- assertEquals(inBounds, outBounds);
- }
-}
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index e7432ac..90c4440 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -136,24 +136,59 @@
free(valueBuffer);
return nullptr;
}
+ mNumShadersCachedInRam++;
+ ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
return SkData::MakeFromMalloc(valueBuffer, valueSize);
}
+namespace {
+// Helper for BlobCache::set to trace the result.
+void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
+ switch (cache->set(key, keySize, value, valueSize)) {
+ case BlobCache::InsertResult::kInserted:
+ // This is what we expect/hope. It means the cache is large enough.
+ return;
+ case BlobCache::InsertResult::kDidClean: {
+ ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
+ valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kNotEnoughSpace: {
+ ATRACE_FORMAT("ShaderCache: could not fit {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kInvalidValueSize:
+ case BlobCache::InsertResult::kInvalidKeySize: {
+ ATRACE_FORMAT("ShaderCache: invalid size {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kKeyTooBig:
+ case BlobCache::InsertResult::kValueTooBig:
+ case BlobCache::InsertResult::kCombinedTooBig: {
+ ATRACE_FORMAT("ShaderCache: entry too big: {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ }
+}
+} // namespace
+
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
if (mInitialized && mBlobCache && mSavePending) {
if (mIDHash.size()) {
auto key = sIDKey;
- mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size());
+ set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
}
mBlobCache->writeToFile();
}
mSavePending = false;
}
-void ShaderCache::store(const SkData& key, const SkData& data) {
+void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
ATRACE_NAME("ShaderCache::store");
std::lock_guard<std::mutex> lock(mMutex);
+ mNumShadersCachedInRam++;
+ ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
if (!mInitialized) {
return;
@@ -187,7 +222,7 @@
mNewPipelineCacheSize = -1;
mTryToStorePipelineCache = true;
}
- bc->set(key.data(), keySize, value, valueSize);
+ set(bc, key.data(), keySize, value, valueSize);
if (!mSavePending && mDeferredSaveDelay > 0) {
mSavePending = true;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 4dcc9fb..3e0fd51 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -73,7 +73,7 @@
* "store" attempts to insert a new key/value blob pair into the cache.
* This will be called by Skia after it compiled a new SKSL shader
*/
- void store(const SkData& key, const SkData& data) override;
+ void store(const SkData& key, const SkData& data, const SkString& description) override;
/**
* "onVkFrameFlushed" tries to store Vulkan pipeline cache state.
@@ -210,6 +210,13 @@
*/
static constexpr uint8_t sIDKey = 0;
+ /**
+ * Most of this class concerns persistent storage for shaders, but it's also
+ * interesting to keep track of how many shaders are stored in RAM. This
+ * class provides a convenient entry point for that.
+ */
+ int mNumShadersCachedInRam = 0;
+
friend class ShaderCacheTestUtils; // used for unit testing
};
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 87981f1..974d85a 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -140,9 +140,9 @@
// write to the in-memory cache without storing on disk and verify we read the same values
sk_sp<SkData> inVS;
setShader(inVS, "sassas");
- ShaderCache::get().store(GrProgramDescTest(100), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
setShader(inVS, "someVS");
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
ASSERT_TRUE(checkShader(outVS, "sassas"));
ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -166,7 +166,7 @@
// change data, store to disk, read back again and verify data has been changed
setShader(inVS, "ewData1");
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
ShaderCache::get().initShaderDiskCache();
ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -177,7 +177,7 @@
std::vector<uint8_t> dataBuffer(dataSize);
genRandomData(dataBuffer);
setShader(inVS, dataBuffer);
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
ShaderCache::get().initShaderDiskCache();
ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -225,7 +225,7 @@
setShader(data, dataBuffer);
blob = std::make_pair(key, data);
- ShaderCache::get().store(*key.get(), *data.get());
+ ShaderCache::get().store(*key.get(), *data.get(), SkString());
}
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 1dc74e5..10ea651 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -106,6 +106,7 @@
PointerController::~PointerController() {
mDisplayInfoListener->onPointerControllerDestroyed();
mUnregisterWindowInfosListener(mDisplayInfoListener);
+ mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
}
std::mutex& PointerController::getLock() const {
@@ -255,6 +256,12 @@
getAdditionalMouseResources = true;
}
mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+ if (viewport.displayId != mLocked.pointerDisplayId) {
+ float xPos, yPos;
+ mCursorController.getPosition(&xPos, &yPos);
+ mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
+ mLocked.pointerDisplayId = viewport.displayId;
+ }
}
void PointerController::updatePointerIcon(int32_t iconId) {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 2e6e851..eab030f 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -104,6 +104,7 @@
struct Locked {
Presentation presentation;
+ int32_t pointerDisplayId = ADISPLAY_ID_NONE;
std::vector<gui::DisplayInfo> mDisplayInfos;
std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 26a65a4..c2bc1e0 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -79,6 +79,7 @@
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
virtual int32_t getDefaultPointerIconId() = 0;
virtual int32_t getCustomPointerIconId() = 0;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
};
/*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index dae1fcc..f9752ed 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -56,9 +56,11 @@
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
virtual int32_t getDefaultPointerIconId() override;
virtual int32_t getCustomPointerIconId() override;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
bool allResourcesAreLoaded();
bool noResourcesAreLoaded();
+ std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; }
private:
void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
@@ -66,6 +68,7 @@
bool pointerIconLoaded{false};
bool pointerResourcesLoaded{false};
bool additionalMouseResourcesLoaded{false};
+ std::optional<int32_t /*displayId*/> latestPointerDisplayId;
};
void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
@@ -126,12 +129,19 @@
icon->hotSpotX = hotSpot.first;
icon->hotSpotY = hotSpot.second;
}
+
+void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
+ float /*xPos*/,
+ float /*yPos*/) {
+ latestPointerDisplayId = displayId;
+}
+
class PointerControllerTest : public Test {
protected:
PointerControllerTest();
~PointerControllerTest();
- void ensureDisplayViewportIsSet();
+ void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -168,9 +178,9 @@
mThread.join();
}
-void PointerControllerTest::ensureDisplayViewportIsSet() {
+void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
DisplayViewport viewport;
- viewport.displayId = ADISPLAY_ID_DEFAULT;
+ viewport.displayId = displayId;
viewport.logicalRight = 1600;
viewport.logicalBottom = 1200;
viewport.physicalRight = 800;
@@ -255,6 +265,30 @@
ensureDisplayViewportIsSet();
}
+TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
+ EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId())
+ << "A pointer display change does not occur when PointerController is created.";
+
+ ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT);
+
+ const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId();
+ ASSERT_TRUE(lastReportedPointerDisplayId)
+ << "The policy is notified of a pointer display change when the viewport is first set.";
+ EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId)
+ << "Incorrect pointer display notified.";
+
+ ensureDisplayViewportIsSet(42);
+
+ EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId())
+ << "The policy is notified when the pointer display changes.";
+
+ // Release the PointerController.
+ mPointerController = nullptr;
+
+ EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId())
+ << "The pointer display changes to invalid when PointerController is destroyed.";
+}
+
class PointerControllerWindowInfoListenerTest : public Test {};
class TestPointerController : public PointerController {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index e8a1a5c..f333b86 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -150,8 +150,6 @@
mBtAdapter = mBtManager.getAdapter();
mBleScanner = mBtAdapter.getBluetoothLeScanner();
mWifiManager = getSystemService(WifiManager.class);
-
- sScanResultsLiveData.setValue(Collections.emptyList());
}
@Override
@@ -175,6 +173,7 @@
@Override
public void onDestroy() {
+ sScanResultsLiveData.setValue(Collections.emptyList());
super.onDestroy();
if (DEBUG) Log.d(TAG, "onDestroy()");
}
@@ -188,6 +187,7 @@
mStopAfterFirstMatch = request.isSingleDevice();
mDiscoveryStarted = true;
sStateLiveData.setValue(DiscoveryState.DISCOVERY_IN_PROGRESS);
+ sScanResultsLiveData.setValue(Collections.emptyList());
final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
final List<BluetoothDeviceFilter> btFilters =
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
index 212ae52..42700b3 100644
--- a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
+++ b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
@@ -60,6 +60,7 @@
android:text="@string/settingslib_learn_more_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
android:clickable="true"
android:visibility="gone"
style="@style/TextAppearance.Footer.Title.SettingsLib"/>
diff --git a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
index d403f9e..1adcead 100644
--- a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
+++ b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
@@ -59,6 +59,7 @@
android:text="@string/settingslib_learn_more_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
android:clickable="true"
android:visibility="gone"
style="@style/TextAppearance.Footer.Title.SettingsLib"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index bdeb474..e6160bb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -16,10 +16,14 @@
package com.android.settingslib;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY;
+
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.annotation.NonNull;
import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Process;
@@ -37,6 +41,8 @@
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
+import com.android.settingslib.utils.BuildCompatUtils;
+
/**
* Version of SwitchPreference that can be disabled by a device admin
* using a user restriction.
@@ -117,8 +123,13 @@
CharSequence switchSummary;
if (mRestrictedSwitchSummary == null) {
- switchSummary = getContext().getText(isChecked()
- ? R.string.enabled_by_admin : R.string.disabled_by_admin);
+ switchSummary = isChecked()
+ ? getUpdatableEnterpriseString(
+ getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+ R.string.enabled_by_admin)
+ : getUpdatableEnterpriseString(
+ getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+ R.string.disabled_by_admin);
} else {
switchSummary = mRestrictedSwitchSummary;
}
@@ -153,6 +164,16 @@
}
}
+ private static String getUpdatableEnterpriseString(
+ Context context, String updatableStringId, int resId) {
+ if (!BuildCompatUtils.isAtLeastT()) {
+ return context.getString(resId);
+ }
+ return context.getSystemService(DevicePolicyManager.class).getResources().getString(
+ updatableStringId,
+ () -> context.getString(resId));
+ }
+
@Override
public void performClick() {
if (!mHelper.performClick()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index c248fff..b0392be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -26,6 +26,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.Context;
@@ -202,6 +203,43 @@
mService.startSearchingForSources(filters);
}
+ /**
+ * Return true if a search has been started by this application.
+ *
+ * @return true if a search has been started by this application
+ * @hide
+ */
+ public boolean isSearchInProgress() {
+ if (DEBUG) {
+ Log.d(TAG, "isSearchInProgress()");
+ }
+ if (mService == null) {
+ Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+ return false;
+ }
+ return mService.isSearchInProgress();
+ }
+
+ /**
+ * Get information about all Broadcast Sources that a Broadcast Sink knows about.
+ *
+ * @param sink Broadcast Sink from which to get all Broadcast Sources
+ * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
+ * stored in the Broadcast Sink
+ * @throws NullPointerException when <var>sink</var> is null
+ */
+ public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources(
+ @NonNull BluetoothDevice sink) {
+ if (DEBUG) {
+ Log.d(TAG, "getAllSources()");
+ }
+ if (mService == null) {
+ Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+ return new ArrayList<BluetoothLeBroadcastReceiveState>();
+ }
+ return mService.getAllSources(sink);
+ }
+
public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mService == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
index 5b1fefb..aff9a6e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -25,6 +25,7 @@
import android.bluetooth.BluetoothLeBroadcastSubgroup;
import android.util.Log;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -95,6 +96,7 @@
}
public String convertToQrCodeString() {
+ String subgroupString = convertSubgroupToString(mSubgroupList);
return new StringBuilder()
.append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)
.append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE)
@@ -122,11 +124,83 @@
.append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS)
- .append(METADATA_START).append(mSubgroupList).append(METADATA_END)
+ .append(METADATA_START).append(subgroupString).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.toString();
}
+ private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) {
+ StringBuilder subgroupListBuilder = new StringBuilder();
+ String subgroupString = "";
+ for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
+ String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
+ String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
+ String channels = convertChannelToString(subgroup.getChannels());
+ subgroupString = new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
+ .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG)
+ .append(METADATA_START).append(audioCodec).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
+ .append(METADATA_START).append(audioContent).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
+ .append(METADATA_START).append(channels).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ subgroupListBuilder.append(subgroupString);
+ }
+ return subgroupListBuilder.toString();
+ }
+
+ private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) {
+ String audioLocation = String.valueOf(config.getAudioLocation());
+ String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8);
+ return new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION)
+ .append(METADATA_START).append(audioLocation).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA)
+ .append(METADATA_START).append(rawMetadata).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ }
+
+ private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) {
+ String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8);
+ return new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO)
+ .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE)
+ .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA)
+ .append(METADATA_START).append(rawMetadata).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ }
+
+ private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) {
+ StringBuilder channelListBuilder = new StringBuilder();
+ String channelString = "";
+ for (BluetoothLeBroadcastChannel channel: channelList) {
+ String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata());
+ channelString = new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX)
+ .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG)
+ .append(METADATA_START).append(channelAudioCodec).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ channelListBuilder.append(channelString);
+ }
+ return channelListBuilder.toString();
+ }
+
/**
* Example : prefix is with the “BT:”, and end by the Android Version.
* BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 20fe495..988055e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -1,9 +1,13 @@
package com.android.settingslib.core;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.RequiresApi;
+import androidx.core.os.BuildCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
@@ -16,9 +20,12 @@
private static final String TAG = "AbstractPrefController";
protected final Context mContext;
+ private final DevicePolicyManager mDevicePolicyManager;
public AbstractPreferenceController(Context context) {
mContext = context;
+ mDevicePolicyManager =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}
/**
@@ -102,4 +109,40 @@
public CharSequence getSummary() {
return null;
}
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ protected void replaceEnterpriseStringTitle(PreferenceScreen screen,
+ String preferenceKey, String overrideKey, int resource) {
+ if (!BuildCompat.isAtLeastT() || mDevicePolicyManager == null) {
+ return;
+ }
+
+ Preference preference = screen.findPreference(preferenceKey);
+ if (preference == null) {
+ Log.d(TAG, "Could not find enterprise preference " + preferenceKey);
+ return;
+ }
+
+ preference.setTitle(
+ mDevicePolicyManager.getResources().getString(overrideKey,
+ () -> mContext.getString(resource)));
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ protected void replaceEnterpriseStringSummary(
+ PreferenceScreen screen, String preferenceKey, String overrideKey, int resource) {
+ if (!BuildCompat.isAtLeastT() || mDevicePolicyManager == null) {
+ return;
+ }
+
+ Preference preference = screen.findPreference(preferenceKey);
+ if (preference == null) {
+ Log.d(TAG, "Could not find enterprise preference " + preferenceKey);
+ return;
+ }
+
+ preference.setSummary(
+ mDevicePolicyManager.getResources().getString(overrideKey,
+ () -> mContext.getString(resource)));
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
index 1b63e3e..1b0738f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
@@ -16,12 +16,16 @@
package com.android.settingslib;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyResourcesManager;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
@@ -30,7 +34,6 @@
import androidx.preference.PreferenceViewHolder;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +48,10 @@
@Mock
private Preference mPreference;
@Mock
+ private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private DevicePolicyResourcesManager mDevicePolicyResourcesManager;
+ @Mock
private RestrictedTopLevelPreference mRestrictedTopLevelPreference;
private PreferenceViewHolder mViewHolder;
@@ -53,18 +60,22 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(mDevicePolicyResourcesManager).when(mDevicePolicyManager)
+ .getResources();
+ doReturn(mDevicePolicyManager).when(mContext)
+ .getSystemService(DevicePolicyManager.class);
mViewHolder = PreferenceViewHolder.createInstanceForTests(mock(View.class));
mHelper = new RestrictedPreferenceHelper(mContext, mPreference, null);
}
@Test
- @Ignore
public void bindPreference_disabled_shouldDisplayDisabledSummary() {
final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
when(mViewHolder.itemView.findViewById(android.R.id.summary))
.thenReturn(summaryView);
when(summaryView.getContext().getText(R.string.disabled_by_admin_summary_text))
.thenReturn("test");
+ when(mDevicePolicyResourcesManager.getString(any(), any())).thenReturn("test");
mHelper.useAdminDisabledSummary(true);
mHelper.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
@@ -75,13 +86,13 @@
}
@Test
- @Ignore
public void bindPreference_notDisabled_shouldNotHideSummary() {
final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
when(mViewHolder.itemView.findViewById(android.R.id.summary))
.thenReturn(summaryView);
when(summaryView.getContext().getText(R.string.disabled_by_admin_summary_text))
.thenReturn("test");
+ when(mDevicePolicyResourcesManager.getString(any(), any())).thenReturn("test");
when(summaryView.getText()).thenReturn("test");
mHelper.useAdminDisabledSummary(true);
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
index 9694999..c4baa52 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
@@ -22,6 +22,4 @@
<!-- Overload default clock widget parameters -->
<dimen name="widget_big_font_size">88dp</dimen>
- <dimen name="qs_panel_padding_top">16dp</dimen>
-
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SystemUI/res-product/values/strings.xml b/packages/SystemUI/res-product/values/strings.xml
index c1e81ba..b71caef 100644
--- a/packages/SystemUI/res-product/values/strings.xml
+++ b/packages/SystemUI/res-product/values/strings.xml
@@ -130,4 +130,8 @@
<!-- Text shown when viewing global actions while phone is locked and additional controls are hidden [CHAR LIMIT=NONE] -->
<string name="global_action_lock_message" product="device">Unlock your device for more options</string>
+ <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
+ <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string>
+ <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] -->
+ <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string>
</resources>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index b230438..10bb6cb 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -121,6 +121,30 @@
android:adjustViewBounds="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/hidden_text_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:text="@string/clipboard_text_hidden"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ <TextView
+ android:id="@+id/hidden_image_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:text="@string/clipboard_text_hidden"
+ android:textColor="#ffffff"
+ android:background="#000000"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
</FrameLayout>
<FrameLayout
android:id="@+id/dismiss_button"
@@ -141,4 +165,4 @@
android:layout_margin="@dimen/overlay_dismiss_button_margin"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fgs_manager_app_item.xml b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
index 30bce9d..a6f659d 100644
--- a/packages/SystemUI/res/layout/fgs_manager_app_item.xml
+++ b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
@@ -50,7 +50,8 @@
<Button
android:id="@+id/fgs_manager_app_item_stop_button"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
android:text="@string/fgs_manager_app_item_stop_button_label"
android:layout_marginStart="12dp"
style="?android:attr/buttonBarNeutralButtonStyle" />
diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml
index 98804fc..c366ceb 100644
--- a/packages/SystemUI/res/layout/qs_paged_page.xml
+++ b/packages/SystemUI/res/layout/qs_paged_page.xml
@@ -19,7 +19,7 @@
android:id="@+id/tile_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingStart="@dimen/notification_side_paddings"
- android:paddingEnd="@dimen/notification_side_paddings"
+ android:paddingStart="@dimen/qs_tiles_page_horizontal_margin"
+ android:paddingEnd="@dimen/qs_tiles_page_horizontal_margin"
android:clipChildren="false"
android:clipToPadding="false" />
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index b3da144..6c42073 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -21,13 +21,20 @@
<dimen name="keyguard_indication_margin_bottom">25dp</dimen>
<dimen name="ambient_indication_margin_bottom">115dp</dimen>
<dimen name="lock_icon_margin_bottom">60dp</dimen>
-
- <dimen name="qs_media_session_height_expanded">172dp</dimen>
-
<!-- margin from keyguard status bar to clock. For split shade it should be
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
+ <!-- QS-->
+ <dimen name="qs_panel_padding_top">16dp</dimen>
+ <dimen name="qs_content_horizontal_padding">24dp</dimen>
+ <dimen name="qs_horizontal_margin">24dp</dimen>
+ <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
+ otherwise full space between two pages is qs_horizontal_margin*2, and that makes tiles page
+ not appear immediately after user swipes to the side -->
+ <dimen name="qs_tiles_page_horizontal_margin">12dp</dimen>
+ <dimen name="qs_media_session_height_expanded">172dp</dimen>
+
<dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
<dimen name="notification_panel_margin_bottom">48dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 33d6f19..f45f106 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -26,6 +26,12 @@
<dimen name="status_bar_header_height_keyguard">56dp</dimen>
<dimen name="qs_media_session_height_expanded">251dp</dimen>
+ <dimen name="qs_content_horizontal_padding">40dp</dimen>
+ <dimen name="qs_horizontal_margin">40dp</dimen>
+ <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
+ otherwise full space between two pages is qs_horizontal_margin*2, and that makes tiles page
+ not appear immediately after user swipes to the side -->
+ <dimen name="qs_tiles_page_horizontal_margin">20dp</dimen>
<dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index fc12d41..2abc9e3 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -27,7 +27,7 @@
<dimen name="qqs_layout_padding_bottom">40dp</dimen>
- <dimen name="notification_panel_margin_horizontal">96dp</dimen>
+ <dimen name="notification_panel_margin_horizontal">80dp</dimen>
<dimen name="notification_side_paddings">40dp</dimen>
<dimen name="notification_section_divider_height">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f77430b..a014efb 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -478,6 +478,22 @@
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
+ <!-- Most of the time it should be the same as notification_side_paddings as it's vertically
+ aligned with notifications. The exception is split shade when this value becomes
+ independent -->
+ <dimen name="qs_horizontal_margin">@dimen/notification_side_paddings</dimen>
+
+ <!-- Most of the time it should be the same as notification_shade_content_margin_horizontal as
+ it's vertically aligned with notifications. The exception is split shade when this value
+ becomes independent -->
+ <dimen name="qs_content_horizontal_padding">@dimen/notification_shade_content_margin_horizontal</dimen>
+
+ <!-- Most of the time it should be the same as notification_side_paddings as it's vertically
+ aligned with notifications. That's not the case on large screen when we have either split
+ shade and QS is not above notifications or in portrait shade when notification scrim is no
+ longer full width and next page of tiles should be at the edge of the screen -->
+ <dimen name="qs_tiles_page_horizontal_margin">@dimen/notification_side_paddings</dimen>
+
<dimen name="qs_customize_internal_side_paddings">8dp</dimen>
<dimen name="qs_icon_size">20dp</dimen>
<dimen name="qs_side_view_size">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8f412e3..2426f01 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1211,7 +1211,7 @@
<string name="wallet_app_button_label">Show all</string>
<!-- Label of the button underneath the card carousel prompting user unlock device. [CHAR LIMIT=NONE] -->
<!-- Secondary label of the quick access wallet tile if no card. [CHAR LIMIT=NONE] -->
- <string name="wallet_secondary_label_no_card">Add a card</string>
+ <string name="wallet_secondary_label_no_card">Tap to open</string>
<!-- Secondary label of the quick access wallet tile if wallet is still updating. [CHAR LIMIT=NONE] -->
<string name="wallet_secondary_label_updating">Updating</string>
<!-- Secondary label of the quick access wallet tile if device locked. [CHAR LIMIT=NONE] -->
@@ -2229,11 +2229,7 @@
<string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string>
<!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
<string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
- <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
- <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string>
- <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] -->
- <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string>
- <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] -->
+ <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIsMIT=50] -->
<string name="media_transfer_failed">Something went wrong. Try again.</string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
@@ -2486,6 +2482,8 @@
<string name="clipboard_edit_image_description">Edit copied image</string>
<!-- Label for button to send copied content to a nearby device [CHAR LIMIT=NONE] -->
<string name="clipboard_send_nearby_description">Send to nearby device</string>
+ <!-- Text informing user that copied content is hidden [CHAR LIMIT=NONE] -->
+ <string name="clipboard_text_hidden">Tap to view</string>
<!-- Generic "add" string [CHAR LIMIT=NONE] -->
<string name="add">Add</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c93c065..f9e73ec 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1111,8 +1111,9 @@
</style>
<style name="FgsManagerAppDuration">
- <item name="android:fontFamily">?android:attr/textAppearanceSmall</item>
<item name="android:textDirection">locale</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="BroadcastDialog">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 6a68c70..98ac640 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -28,7 +28,6 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -169,7 +168,7 @@
private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final FalsingCollector mFalsingCollector;
private final DevicePostureController mDevicePostureController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardViewController mKeyguardViewController;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -181,7 +180,7 @@
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController.Factory emergencyButtonControllerFactory,
DevicePostureController devicePostureController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ KeyguardViewController keyguardViewController) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -194,7 +193,7 @@
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
mDevicePostureController = devicePostureController;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mKeyguardViewController = keyguardViewController;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -215,7 +214,7 @@
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
- mFalsingCollector, mStatusBarKeyguardViewManager);
+ mFalsingCollector, mKeyguardViewController);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 1903526..8f44e97 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -43,7 +43,6 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
@@ -56,7 +55,7 @@
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final InputMethodManager mInputMethodManager;
private final DelayableExecutor mMainExecutor;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardViewController mKeyguardViewController;
private final boolean mShowImeAtScreenOn;
private EditText mPasswordEntry;
private ImageView mSwitchImeButton;
@@ -119,14 +118,14 @@
@Main DelayableExecutor mainExecutor,
@Main Resources resources,
FalsingCollector falsingCollector,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ KeyguardViewController keyguardViewController) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mKeyguardViewController = keyguardViewController;
mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
@@ -209,7 +208,7 @@
}
private void showInput() {
- if (!mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ if (!mKeyguardViewController.isBouncerShowing()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 41f9240..39c3949 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -282,7 +282,7 @@
super.reloadColors();
mMessageAreaController.reloadColors();
int textColor = Utils.getColorAttr(mLockPatternView.getContext(),
- android.R.attr.textColorPrimary).getDefaultColor();
+ android.R.attr.textColorSecondary).getDefaultColor();
int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor();
mLockPatternView.setColors(textColor, textColor, errorColor);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index caf7ee4..c91c899 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -30,7 +30,6 @@
import androidx.annotation.StyleRes;
-import com.android.settingslib.Utils;
import com.android.systemui.animation.Interpolators;
/**
@@ -42,24 +41,29 @@
private ValueAnimator mContractAnimator;
private AnimatorSet mContractAnimatorSet;
private GradientDrawable mBackground;
- private int mNormalColor;
- private int mHighlightColor;
- private int mStyle;
+ private Drawable mImageButton;
private TextView mDigitTextView;
+ private int mNormalBackgroundColor;
+ private int mPressedBackgroundColor;
+ private int mTextColorPrimary;
+ private int mTextColorPressed;
+ private int mStyle;
private static final int EXPAND_ANIMATION_MS = 100;
private static final int EXPAND_COLOR_ANIMATION_MS = 50;
private static final int CONTRACT_ANIMATION_DELAY_MS = 33;
private static final int CONTRACT_ANIMATION_MS = 417;
- NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style) {
- this(context, drawable, style, null);
+ NumPadAnimator(Context context, final Drawable drawable,
+ @StyleRes int style, Drawable buttonImage) {
+ this(context, drawable, style, null, buttonImage);
}
NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style,
- @Nullable TextView digitTextView) {
+ @Nullable TextView digitTextView, @Nullable Drawable buttonImage) {
mStyle = style;
mBackground = (GradientDrawable) drawable;
mDigitTextView = digitTextView;
+ mImageButton = buttonImage;
reloadColors(context);
}
@@ -88,26 +92,28 @@
* Reload colors from resources.
**/
void reloadColors(Context context) {
- int[] customAttrs = {android.R.attr.colorControlNormal,
- android.R.attr.colorControlHighlight};
+ boolean isNumPadKey = mImageButton == null;
+ int[] customAttrs = {android.R.attr.colorControlNormal};
ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
TypedArray a = ctw.obtainStyledAttributes(customAttrs);
- mNormalColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
+ mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
com.android.internal.R.attr.colorSurface);
- mHighlightColor = a.getColor(1, 0);
a.recycle();
+ mBackground.setColor(mNormalBackgroundColor);
- mBackground.setColor(mNormalColor);
- createAnimators(context);
+ mPressedBackgroundColor = context.getColor(android.R.color.system_accent1_200);
+ mTextColorPrimary = isNumPadKey
+ ? com.android.settingslib.Utils
+ .getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+ : com.android.settingslib.Utils
+ .getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse);
+ mTextColorPressed = com.android.settingslib.Utils
+ .getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent);
+ createAnimators();
}
- private void createAnimators(Context context) {
- int textColorPrimary = Utils.getColorAttrDefaultColor(context,
- android.R.attr.textColorPrimary);
- int textColorPrimaryInverse = Utils.getColorAttrDefaultColor(context,
- android.R.attr.textColorPrimaryInverse);
-
+ private void createAnimators() {
// Actual values will be updated later, usually during an onLayout() call
mExpandAnimator = ValueAnimator.ofFloat(0f, 1f);
mExpandAnimator.setDuration(EXPAND_ANIMATION_MS);
@@ -116,7 +122,7 @@
anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue()));
ValueAnimator expandBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
- mNormalColor, mHighlightColor);
+ mNormalBackgroundColor, mPressedBackgroundColor);
expandBackgroundColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS);
expandBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
expandBackgroundColorAnimator.addUpdateListener(
@@ -124,13 +130,16 @@
ValueAnimator expandTextColorAnimator =
ValueAnimator.ofObject(new ArgbEvaluator(),
- textColorPrimary, textColorPrimaryInverse);
+ mTextColorPrimary, mTextColorPressed);
expandTextColorAnimator.setInterpolator(Interpolators.LINEAR);
expandTextColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS);
expandTextColorAnimator.addUpdateListener(valueAnimator -> {
if (mDigitTextView != null) {
mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue());
}
+ if (mImageButton != null) {
+ mImageButton.setTint((int) valueAnimator.getAnimatedValue());
+ }
});
mExpandAnimatorSet = new AnimatorSet();
@@ -144,7 +153,7 @@
mContractAnimator.addUpdateListener(
anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue()));
ValueAnimator contractBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
- mHighlightColor, mNormalColor);
+ mPressedBackgroundColor, mNormalBackgroundColor);
contractBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
contractBackgroundColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS);
contractBackgroundColorAnimator.setDuration(CONTRACT_ANIMATION_MS);
@@ -152,8 +161,8 @@
animator -> mBackground.setColor((int) animator.getAnimatedValue()));
ValueAnimator contractTextColorAnimator =
- ValueAnimator.ofObject(new ArgbEvaluator(), textColorPrimaryInverse,
- textColorPrimary);
+ ValueAnimator.ofObject(new ArgbEvaluator(), mTextColorPressed,
+ mTextColorPrimary);
contractTextColorAnimator.setInterpolator(Interpolators.LINEAR);
contractTextColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS);
contractTextColorAnimator.setDuration(CONTRACT_ANIMATION_MS);
@@ -161,6 +170,9 @@
if (mDigitTextView != null) {
mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue());
}
+ if (mImageButton != null) {
+ mImageButton.setTint((int) valueAnimator.getAnimatedValue());
+ }
});
mContractAnimatorSet = new AnimatorSet();
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 7b98f27..8099f75 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -42,7 +42,7 @@
Drawable background = getBackground();
if (background instanceof GradientDrawable) {
mAnimator = new NumPadAnimator(context, background.mutate(),
- attrs.getStyleAttribute());
+ attrs.getStyleAttribute(), getDrawable());
} else {
mAnimator = null;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index f771c97..4aed251 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -134,7 +134,7 @@
Drawable background = getBackground();
if (background instanceof GradientDrawable) {
mAnimator = new NumPadAnimator(context, background.mutate(),
- R.style.NumPadKey, mDigitText);
+ R.style.NumPadKey, mDigitText, null);
} else {
mAnimator = null;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 6e06130..334bb1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -76,12 +76,6 @@
void playTrustedSound();
/**
- * When the bouncer is shown or hides
- * @param shown
- */
- void onBouncerVisiblityChanged(boolean shown);
-
- /**
* @return true if the screen is on
*/
boolean isScreenOn();
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 8d65098..cbce854 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -899,8 +899,8 @@
final DisplayInfo displayInfo = new DisplayInfo();
mContext.getDisplay().getDisplayInfo(displayInfo);
return DisplayUtils.getPhysicalPixelDisplaySizeRatio(
- stableDisplaySize.x, stableDisplaySize.y, displayInfo.logicalWidth,
- displayInfo.logicalHeight);
+ stableDisplaySize.x, stableDisplaySize.y, displayInfo.getNaturalWidth(),
+ displayInfo.getNaturalHeight());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d59ad92..9324893 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -278,6 +278,7 @@
}
});
mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation);
+ mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination);
mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect();
updateUdfpsLocation();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 76c1dbc..d9aa1ba 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -80,6 +80,7 @@
@VisibleForTesting
internal var startLightRevealScrimOnKeyguardFadingAway = false
+ var lightRevealScrimAnimator: ValueAnimator? = null
var fingerprintSensorLocation: PointF? = null
private var faceSensorLocation: PointF? = null
private var circleReveal: LightRevealEffect? = null
@@ -163,7 +164,8 @@
if (keyguardStateController.isKeyguardFadingAway) {
val lightRevealScrim = centralSurfaces.lightRevealScrim
if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
- ValueAnimator.ofFloat(.1f, 1f).apply {
+ lightRevealScrimAnimator?.cancel()
+ lightRevealScrimAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = RIPPLE_ANIMATION_DURATION
startDelay = keyguardStateController.keyguardFadingAwayDelay
@@ -183,6 +185,8 @@
if (lightRevealScrim.revealEffect == circleReveal) {
lightRevealScrim.revealEffect = LiftReveal
}
+
+ lightRevealScrimAnimator = null
}
})
start()
@@ -192,6 +196,13 @@
}
}
+ /**
+ * Whether we're animating the light reveal scrim from a call to [onKeyguardFadingAwayChanged].
+ */
+ fun isAnimatingLightRevealScrim(): Boolean {
+ return lightRevealScrimAnimator?.isRunning ?: false
+ }
+
override fun onStartedGoingToSleep() {
// reset the light reveal start in case we were pending an unlock
startLightRevealScrimOnKeyguardFadingAway = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 432d293..a35f042 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -131,6 +131,7 @@
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting int mSensorId;
+ private boolean mHalControlsIllumination;
@VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -201,9 +202,10 @@
mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
mLockscreenShadeTransitionController, mConfigurationController,
mSystemClock, mKeyguardStateController,
- mUnlockedScreenOffAnimationController, mHbmProvider, requestId, reason,
- callback, (view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator)));
+ mUnlockedScreenOffAnimationController, mHalControlsIllumination,
+ mHbmProvider, requestId, reason, callback,
+ (view, event, fromUdfpsView) -> onTouch(requestId, event,
+ fromUdfpsView), mActivityLaunchAnimator)));
}
@Override
@@ -310,6 +312,11 @@
mAuthControllerUpdateUdfpsLocation = r;
}
+ // TODO(b/229290039): UDFPS controller should manage its properties on its own. Remove this.
+ public void setHalControlsIllumination(boolean value) {
+ mHalControlsIllumination = value;
+ }
+
/**
* Calculate the pointer speed given a velocity tracker and the pointer id.
* This assumes that the velocity tracker has already been passed all relevant motion events.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 9c8aee4..2d51c97 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -77,6 +77,7 @@
private val systemClock: SystemClock,
private val keyguardStateController: KeyguardStateController,
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private val halControlsIllumination: Boolean,
private var hbmProvider: UdfpsHbmProvider,
val requestId: Long,
@ShowReason val requestReason: Int,
@@ -137,6 +138,7 @@
R.layout.udfps_view, null, false
) as UdfpsView).apply {
overlayParams = params
+ halControlsIllumination = this@UdfpsControllerOverlay.halControlsIllumination
setHbmProvider(hbmProvider)
val animation = inflateUdfpsAnimation(this, controller)
if (animation != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
index 38c937f..f26dd5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
@@ -34,8 +34,11 @@
* invoked from the UI thread.
*
* @param onHbmEnabled A runnable that will be executed once HBM is enabled.
+ *
+ * TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
+ * This only makes sense now because vendor code may rely on the side effects of enableHbm.
*/
- void enableHbm(@Nullable Runnable onHbmEnabled);
+ void enableHbm(boolean halControlsIllumination, @Nullable Runnable onHbmEnabled);
/**
* UdfpsView will call this to disable HBM when illumination is no longer needed.
@@ -46,8 +49,6 @@
* The call must be made from the UI thread. The callback, if provided, will also be invoked
* from the UI thread.
*
- *
- *
* @param onHbmDisabled A runnable that will be executed once HBM is disabled.
*/
void disableHbm(@Nullable Runnable onHbmDisabled);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 2aa345a..245c225 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -63,9 +63,12 @@
/** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
var animationViewController: UdfpsAnimationViewController<*>? = null
- /** Parameters that affect the position and size of the overlay. Visible for testing. */
+ /** Parameters that affect the position and size of the overlay. */
var overlayParams = UdfpsOverlayParams()
+ /** Whether the HAL is responsible for enabling and disabling of LHBM. */
+ var halControlsIllumination: Boolean = true
+
/** Debug message. */
var debugMessage: String? = null
set(value) {
@@ -154,11 +157,17 @@
}
private fun doIlluminate(onIlluminatedRunnable: Runnable?) {
- hbmProvider?.enableHbm() {
+ // TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
+ // This only makes sense now because vendor code may rely on the side effects of enableHbm.
+ hbmProvider?.enableHbm(halControlsIllumination) {
if (onIlluminatedRunnable != null) {
- // No framework API can reliably tell when a frame reaches the panel. A timeout
- // is the safest solution.
- postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
+ if (halControlsIllumination) {
+ onIlluminatedRunnable.run()
+ } else {
+ // No framework API can reliably tell when a frame reaches the panel. A timeout
+ // is the safest solution.
+ postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
+ }
} else {
Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null")
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 726d00c..ee8363f 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -38,6 +38,7 @@
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -128,6 +129,8 @@
private final View mClipboardPreview;
private final ImageView mImagePreview;
private final TextView mTextPreview;
+ private final TextView mHiddenTextPreview;
+ private final TextView mHiddenImagePreview;
private final View mPreviewBorder;
private final OverlayActionChip mEditChip;
private final OverlayActionChip mRemoteCopyChip;
@@ -186,6 +189,8 @@
mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+ mHiddenTextPreview = requireNonNull(mView.findViewById(R.id.hidden_text_preview));
+ mHiddenImagePreview = requireNonNull(mView.findViewById(R.id.hidden_image_preview));
mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
@@ -270,21 +275,32 @@
mExitAnimator.cancel();
}
reset();
+
+ boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+ && clipData.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
if (clipData == null || clipData.getItemCount() == 0) {
- showTextPreview(mContext.getResources().getString(
- R.string.clipboard_overlay_text_copied));
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
ClipData.Item item = clipData.getItemAt(0);
if (item.getTextLinks() != null) {
AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
}
- showEditableText(item.getText());
+ if (isSensitive) {
+ showEditableText(
+ mContext.getResources().getString(R.string.clipboard_text_hidden), true);
+ } else {
+ showEditableText(item.getText(), false);
+ }
} else if (clipData.getItemAt(0).getUri() != null) {
// How to handle non-image URIs?
- showEditableImage(clipData.getItemAt(0).getUri());
+ showEditableImage(clipData.getItemAt(0).getUri(), isSensitive);
} else {
showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
}
Intent remoteCopyIntent = getRemoteCopyIntent(clipData);
// Only show remote copy if it's available.
@@ -406,15 +422,23 @@
animateOut();
}
- private void showTextPreview(CharSequence text) {
- mTextPreview.setVisibility(View.VISIBLE);
+ private void showSinglePreview(View v) {
+ mTextPreview.setVisibility(View.GONE);
mImagePreview.setVisibility(View.GONE);
- mTextPreview.setText(text.subSequence(0, Math.min(500, text.length())));
+ mHiddenTextPreview.setVisibility(View.GONE);
+ mHiddenImagePreview.setVisibility(View.GONE);
+ v.setVisibility(View.VISIBLE);
+ }
+
+ private void showTextPreview(CharSequence text, TextView textView) {
+ showSinglePreview(textView);
+ textView.setText(text.subSequence(0, Math.min(500, text.length())));
mEditChip.setVisibility(View.GONE);
}
- private void showEditableText(CharSequence text) {
- showTextPreview(text);
+ private void showEditableText(CharSequence text, boolean hidden) {
+ TextView textView = hidden ? mHiddenTextPreview : mTextPreview;
+ showTextPreview(text, textView);
mEditChip.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
mEditChip.setAlpha(1f);
@@ -422,32 +446,36 @@
mContext.getString(R.string.clipboard_edit_text_description));
View.OnClickListener listener = v -> editText();
mEditChip.setOnClickListener(listener);
- mTextPreview.setOnClickListener(listener);
+ textView.setOnClickListener(listener);
}
- private void showEditableImage(Uri uri) {
- ContentResolver resolver = mContext.getContentResolver();
- try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
- // The width of the view is capped, height maintains aspect ratio, so allow it to be
- // taller if needed.
- Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- mImagePreview.setImageBitmap(thumbnail);
- } catch (IOException e) {
- Log.e(TAG, "Thumbnail loading failed", e);
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
- return;
- }
- mTextPreview.setVisibility(View.GONE);
- mImagePreview.setVisibility(View.VISIBLE);
+ private void showEditableImage(Uri uri, boolean isSensitive) {
mEditChip.setAlpha(1f);
mActionContainerBackground.setVisibility(View.VISIBLE);
View.OnClickListener listener = v -> editImage(uri);
+ if (isSensitive) {
+ showSinglePreview(mHiddenImagePreview);
+ mHiddenImagePreview.setOnClickListener(listener);
+ } else {
+ showSinglePreview(mImagePreview);
+ ContentResolver resolver = mContext.getContentResolver();
+ try {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+ // The width of the view is capped, height maintains aspect ratio, so allow it to be
+ // taller if needed.
+ Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+ mImagePreview.setImageBitmap(thumbnail);
+ } catch (IOException e) {
+ Log.e(TAG, "Thumbnail loading failed", e);
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ }
+ mImagePreview.setOnClickListener(listener);
+ }
mEditChip.setOnClickListener(listener);
mEditChip.setContentDescription(
mContext.getString(R.string.clipboard_edit_image_description));
- mImagePreview.setOnClickListener(listener);
}
private Intent getRemoteCopyIntent(ClipData clipData) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index 0d89879..b54b832 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -20,10 +20,12 @@
import android.app.Activity;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -41,6 +43,7 @@
private EditText mEditText;
private ClipboardManager mClipboardManager;
private TextView mAttribution;
+ private boolean mSensitive;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -72,6 +75,9 @@
}
mEditText.setText(clip.getItemAt(0).getText());
mEditText.requestFocus();
+ mSensitive = clip.getDescription().getExtras() != null
+ && clip.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
mClipboardManager.addPrimaryClipChangedListener(this);
}
@@ -88,6 +94,9 @@
private void saveToClipboard() {
ClipData clip = ClipData.newPlainText("text", mEditText.getText());
+ PersistableBundle extras = new PersistableBundle();
+ extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, mSensitive);
+ clip.getDescription().setExtras(extras);
mClipboardManager.setPrimaryClip(clip);
hideImeAndFinish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index f9115b2..3eb58bb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -343,7 +343,7 @@
info.className = Switch::class.java.name
}
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
if (super.performAccessibilityAction(host, action, args)) {
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index fb09132..14a7e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -44,6 +44,7 @@
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
import com.android.systemui.shared.system.smartspace.SmartspaceState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
@@ -140,7 +141,8 @@
keyguardViewMediator: Lazy<KeyguardViewMediator>,
private val keyguardViewController: KeyguardViewController,
private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -372,7 +374,8 @@
* changed.
*/
override fun onKeyguardGoingAwayChanged() {
- if (keyguardStateController.isKeyguardGoingAway) {
+ if (keyguardStateController.isKeyguardGoingAway
+ && !statusBarStateController.leaveOpenOnKeyguardHide()) {
prepareForInWindowLauncherAnimations()
}
}
@@ -813,6 +816,11 @@
return false
}
+ // The smartspace is not visible if the bouncer is showing, so don't shared element it.
+ if (keyguardStateController.isBouncerShowing) {
+ return false
+ }
+
// We started to swipe to dismiss, but now we're doing a fling animation to complete the
// dismiss. In this case, the smartspace swiped away with the rest of the keyguard, so don't
// do the shared element transition.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 10ea1e0..4d59f1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -779,16 +779,6 @@
}
@Override
- public void onBouncerVisiblityChanged(boolean shown) {
- synchronized (KeyguardViewMediator.this) {
- if (shown) {
- mPendingPinLock = false;
- }
- adjustStatusBarLocked(shown, false);
- }
- }
-
- @Override
public void playTrustedSound() {
KeyguardViewMediator.this.playTrustedSound();
}
@@ -921,12 +911,12 @@
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ setOccluded(false /* isOccluded */, true /* animate */);
+
if (apps == null || apps.length == 0 || apps[0] == null) {
Log.d(TAG, "No apps provided to unocclude runner; "
+ "skipping animation and unoccluding.");
-
finishedCallback.onAnimationFinished();
- setOccluded(false /* isOccluded */, true /* animate */);
return;
}
@@ -971,7 +961,6 @@
@Override
public void onAnimationEnd(Animator animation) {
try {
- setOccluded(false /* isOccluded */, true /* animate */);
finishedCallback.onAnimationFinished();
mUnoccludeAnimator = null;
} catch (RemoteException e) {
@@ -989,6 +978,19 @@
private DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onBouncerShowingChanged() {
+ synchronized (KeyguardViewMediator.this) {
+ if (mKeyguardStateController.isBouncerShowing()) {
+ mPendingPinLock = false;
+ }
+ adjustStatusBarLocked(mKeyguardStateController.isBouncerShowing(), false);
+ }
+ }
+ };
+
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
@@ -1059,6 +1061,7 @@
statusBarStateController.addCallback(this);
mKeyguardStateController = keyguardStateController;
+ keyguardStateController.addCallback(mKeyguardStateControllerCallback);
mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
mScreenOffAnimationController = screenOffAnimationController;
mInteractionJankMonitor = interactionJankMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 5bb6557..8dcca3d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -449,7 +449,7 @@
abstract int getStopButtonVisibility();
public CharSequence getStopButtonText() {
- return mContext.getText(R.string.keyboard_key_media_stop);
+ return mContext.getText(R.string.media_output_dialog_button_stop_casting);
}
public void onStopButtonClick() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index e7f97d2..58b6ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -55,8 +55,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
-import androidx.mediarouter.media.MediaRouter;
-import androidx.mediarouter.media.MediaRouterParams;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
@@ -215,9 +213,8 @@
}
boolean shouldShowLaunchSection() {
- MediaRouterParams routerParams = MediaRouter.getInstance(mContext).getRouterParams();
- Log.d(TAG, "try to get routerParams: " + routerParams);
- return routerParams != null && !routerParams.isMediaTransferReceiverEnabled();
+ // TODO(b/231398073): Implements this when available.
+ return false;
}
void setRefreshing(boolean refreshing) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 9248433..026a305 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -104,7 +104,7 @@
@Override
public CharSequence getStopButtonText() {
- int resId = R.string.keyboard_key_media_stop;
+ int resId = R.string.media_output_dialog_button_stop_casting;
if (isBroadcastSupported() && mMediaOutputController.isPlaying()
&& !mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
resId = R.string.media_output_broadcast;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c7cd48f..3bb4e64 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -125,6 +125,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -203,6 +204,7 @@
private final UiEventLogger mUiEventLogger;
private final NavBarHelper mNavBarHelper;
private final NotificationShadeDepthController mNotificationShadeDepthController;
+ private final UserContextProvider mUserContextProvider;
private NavigationBarFrame mFrame;
private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -514,7 +516,8 @@
DeadZone deadZone,
DeviceConfigProxy deviceConfigProxy,
NavigationBarTransitions navigationBarTransitions,
- Optional<BackAnimation> backAnimation) {
+ Optional<BackAnimation> backAnimation,
+ UserContextProvider userContextProvider) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -550,6 +553,7 @@
mAutoHideControllerFactory = autoHideControllerFactory;
mTelecomManagerOptional = telecomManagerOptional;
mInputMethodManager = inputMethodManager;
+ mUserContextProvider = userContextProvider;
mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
}
@@ -1512,35 +1516,36 @@
int insetsHeight = -1;
int gravity = Gravity.BOTTOM;
boolean navBarCanMove = true;
+ final Context userContext = mUserContextProvider.createCurrentUserContext(mContext);
if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) {
Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
navBarCanMove = displaySize.width() != displaySize.height()
- && mContext.getResources().getBoolean(
+ && userContext.getResources().getBoolean(
com.android.internal.R.bool.config_navBarCanMove);
}
if (!navBarCanMove) {
- height = mContext.getResources().getDimensionPixelSize(
+ height = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
- insetsHeight = mContext.getResources().getDimensionPixelSize(
+ insetsHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
} else {
switch (rotation) {
case ROTATION_UNDEFINED:
case Surface.ROTATION_0:
case Surface.ROTATION_180:
- height = mContext.getResources().getDimensionPixelSize(
+ height = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
- insetsHeight = mContext.getResources().getDimensionPixelSize(
+ insetsHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
break;
case Surface.ROTATION_90:
gravity = Gravity.RIGHT;
- width = mContext.getResources().getDimensionPixelSize(
+ width = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_width);
break;
case Surface.ROTATION_270:
gravity = Gravity.LEFT;
- width = mContext.getResources().getDimensionPixelSize(
+ width = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_width);
break;
}
@@ -1565,12 +1570,12 @@
lp.providedInternalInsets[ITYPE_NAVIGATION_BAR] = null;
}
lp.token = new Binder();
- lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
+ lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
| WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.windowAnimations = 0;
- lp.setTitle("NavigationBar" + mContext.getDisplayId());
+ lp.setTitle("NavigationBar" + userContext.getDisplayId());
lp.setFitInsetsTypes(0 /* types */);
lp.setTrustedOverlay();
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 3eb4b10..3e8cdf3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -420,7 +420,7 @@
PowerExemptionManager.REASON_SYSTEM_UID,
PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
- PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE,
+ PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED,
PowerExemptionManager.REASON_DEVICE_OWNER,
PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
PowerExemptionManager.REASON_DPO_PROTECTED_APP,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7d3df6e..5d2060d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -47,9 +47,10 @@
private QSCustomizer mQSCustomizer;
private NonInterceptingScrollView mQSPanelContainer;
- private int mSideMargins;
+ private int mHorizontalMargins;
+ private int mTilesPageMargin;
private boolean mQsDisabled;
- private int mContentPadding = -1;
+ private int mContentHorizontalPadding = -1;
private boolean mClippingEnabled;
public QSContainerImpl(Context context, AttributeSet attrs) {
@@ -145,12 +146,17 @@
mQSPanelContainer.getPaddingEnd(),
bottomPadding);
- int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
- int padding = getResources().getDimensionPixelSize(
- R.dimen.notification_shade_content_margin_horizontal);
- boolean marginsChanged = padding != mContentPadding || sideMargins != mSideMargins;
- mContentPadding = padding;
- mSideMargins = sideMargins;
+ int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin);
+ int horizontalPadding = getResources().getDimensionPixelSize(
+ R.dimen.qs_content_horizontal_padding);
+ int tilesPageMargin = getResources().getDimensionPixelSize(
+ R.dimen.qs_tiles_page_horizontal_margin);
+ boolean marginsChanged = horizontalPadding != mContentHorizontalPadding
+ || horizontalMargins != mHorizontalMargins
+ || tilesPageMargin != mTilesPageMargin;
+ mContentHorizontalPadding = horizontalPadding;
+ mHorizontalMargins = horizontalMargins;
+ mTilesPageMargin = tilesPageMargin;
if (marginsChanged) {
updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
}
@@ -198,22 +204,22 @@
// Only padding for FooterActionsView, no margin. That way, the background goes
// all the way to the edge.
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- lp.rightMargin = mSideMargins;
- lp.leftMargin = mSideMargins;
+ lp.rightMargin = mHorizontalMargins;
+ lp.leftMargin = mHorizontalMargins;
}
if (view == mQSPanelContainer) {
// QS panel lays out some of its content full width
- qsPanelController.setContentMargins(mContentPadding, mContentPadding);
- // Set it as double the side margin (to simulate end margin of current page +
- // start margin of next page).
- qsPanelController.setPageMargin(mSideMargins);
+ qsPanelController.setContentMargins(mContentHorizontalPadding,
+ mContentHorizontalPadding);
+ qsPanelController.setPageMargin(mTilesPageMargin);
} else if (view == mHeader) {
- quickStatusBarHeaderController.setContentMargins(mContentPadding, mContentPadding);
+ quickStatusBarHeaderController.setContentMargins(mContentHorizontalPadding,
+ mContentHorizontalPadding);
} else {
view.setPaddingRelative(
- mContentPadding,
+ mContentHorizontalPadding,
view.getPaddingTop(),
- mContentPadding,
+ mContentHorizontalPadding,
view.getPaddingBottom());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 772e9fa..248c78e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -147,16 +147,9 @@
if (mController.getWalletClient().isWalletServiceAvailable()
&& mController.getWalletClient().isWalletFeatureAvailable()) {
if (mSelectedCard != null) {
- if (isDeviceLocked) {
- state.state = Tile.STATE_INACTIVE;
- state.secondaryLabel =
- mContext.getString(R.string.wallet_secondary_label_device_locked);
- state.sideViewCustomDrawable = null;
- } else {
- state.state = Tile.STATE_ACTIVE;
- state.secondaryLabel = mSelectedCard.getContentDescription();
- state.sideViewCustomDrawable = mCardViewDrawable;
- }
+ state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
+ state.secondaryLabel = mSelectedCard.getContentDescription();
+ state.sideViewCustomDrawable = mCardViewDrawable;
} else {
state.state = Tile.STATE_INACTIVE;
state.secondaryLabel =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index f4dd415..d99c1d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -92,15 +92,15 @@
@Override
protected void handleClick(@Nullable View view) {
- if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
- !mSensorPrivacyController.isSensorBlocked(getSensorId()));
- });
+ boolean blocked = mSensorPrivacyController.isSensorBlocked(getSensorId());
+ if (mSensorPrivacyController.requiresAuthentication()
+ && mKeyguard.isMethodSecure()
+ && mKeyguard.isShowing()) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(() ->
+ mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked));
return;
}
- mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
- !mSensorPrivacyController.isSensorBlocked(getSensorId()));
+ mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index dae375a..2d1d8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -134,7 +134,9 @@
override fun onClick(dialog: DialogInterface?, which: Int) {
when (which) {
BUTTON_POSITIVE -> {
- if (keyguardStateController.isMethodSecure && keyguardStateController.isShowing) {
+ if (sensorPrivacyController.requiresAuthentication() &&
+ keyguardStateController.isMethodSecure &&
+ keyguardStateController.isShowing) {
keyguardDismissUtil.executeWhenUnlocked({
bgHandler.postDelayed({
disableSensorPrivacy()
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
index 27af152..dae8512 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
@@ -23,4 +23,10 @@
*/
interface UserContextProvider {
val userContext: Context
+
+ /**
+ * Creates the {@code context} with the current user.
+ * @see Context#createContextAsUser(UserHandle, int)
+ */
+ fun createCurrentUserContext(context: Context): Context
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 3a6248b..80d5f16 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -127,6 +127,12 @@
}
}
+ override fun createCurrentUserContext(context: Context): Context {
+ synchronized(mutex) {
+ return context.createContextAsUser(userHandle, 0)
+ }
+ }
+
private fun setUserIdInternal(user: Int): Pair<Context, List<UserInfo>> {
val profiles = userManager.getProfiles(user)
val handle = UserHandle(user)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index ebd610b..0c9e1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -180,13 +180,6 @@
default void setRequestTopUi(boolean requestTopUi, String componentTag) {}
/**
- * Under low light conditions, we might want to increase the display brightness on devices that
- * don't have an IR camera.
- * @param brightness float from 0 to 1 or {@code LayoutParams.BRIGHTNESS_OVERRIDE_NONE}
- */
- default void setFaceAuthDisplayBrightness(float brightness) {}
-
- /**
* If {@link LightRevealScrim} obscures the UI.
* @param opaque if the scrim is opaque
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index a390e9f..15ad312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -20,11 +20,13 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
@@ -39,12 +41,40 @@
@CoordinatorScope
class ConversationCoordinator @Inject constructor(
private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+ private val conversationIconManager: ConversationIconManager,
@PeopleHeader peopleHeaderController: NodeController
) : Coordinator {
+ private val promotedEntriesToSummaryOfSameChannel =
+ mutableMapOf<NotificationEntry, NotificationEntry>()
+
+ private val onBeforeRenderListListener = OnBeforeRenderListListener { _ ->
+ val unimportantSummaries = promotedEntriesToSummaryOfSameChannel
+ .mapNotNull { (promoted, summary) ->
+ val originalGroup = summary.parent
+ when {
+ originalGroup == null -> null
+ originalGroup == promoted.parent -> null
+ originalGroup.parent == null -> null
+ originalGroup.summary != summary -> null
+ originalGroup.children.any { it.channel == summary.channel } -> null
+ else -> summary.key
+ }
+ }
+ conversationIconManager.setUnimportantConversations(unimportantSummaries)
+ promotedEntriesToSummaryOfSameChannel.clear()
+ }
+
private val notificationPromoter = object : NotifPromoter(TAG) {
override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean {
- return entry.channel?.isImportantConversation == true
+ val shouldPromote = entry.channel?.isImportantConversation == true
+ if (shouldPromote) {
+ val summary = entry.parent?.summary
+ if (summary != null && entry.channel == summary.channel) {
+ promotedEntriesToSummaryOfSameChannel[entry] = summary
+ }
+ }
+ return shouldPromote
}
}
@@ -67,6 +97,7 @@
override fun attach(pipeline: NotifPipeline) {
pipeline.addPromoter(notificationPromoter)
+ pipeline.addOnBeforeRenderListListener(onBeforeRenderListListener)
}
private fun isConversation(entry: ListEntry): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 386e2d3..f460644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -18,7 +18,6 @@
import android.annotation.MainThread
import android.view.View
-import com.android.systemui.util.kotlin.transform
import com.android.systemui.util.traceSection
/**
@@ -41,7 +40,6 @@
) {
private val rootNode = ShadeNode(rootController)
private val nodes = mutableMapOf(rootController to rootNode)
- private val views = mutableMapOf<View, ShadeNode>()
/**
* Adds and removes views from the root (and its children) until their structure matches the
@@ -66,26 +64,25 @@
*
* For debugging purposes.
*/
- fun getViewLabel(view: View): String = views[view]?.label ?: view.toString()
+ fun getViewLabel(view: View): String =
+ nodes.values.firstOrNull { node -> node.view === view }?.label ?: view.toString()
- private fun detachChildren(
- parentNode: ShadeNode,
- specMap: Map<NodeController, NodeSpec>
- ) {
- val parentSpec = specMap[parentNode.controller]
-
- for (i in parentNode.getChildCount() - 1 downTo 0) {
- val childView = parentNode.getChildAt(i)
- views[childView]?.let { childNode ->
- val childSpec = specMap[childNode.controller]
-
- maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
-
- if (childNode.controller.getChildCount() > 0) {
- detachChildren(childNode, specMap)
+ private fun detachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
+ val views = nodes.values.associateBy { it.view }
+ fun detachRecursively(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
+ val parentSpec = specMap[parentNode.controller]
+ for (i in parentNode.getChildCount() - 1 downTo 0) {
+ val childView = parentNode.getChildAt(i)
+ views[childView]?.let { childNode ->
+ val childSpec = specMap[childNode.controller]
+ maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
+ if (childNode.controller.getChildCount() > 0) {
+ detachRecursively(childNode, specMap)
+ }
}
}
}
+ detachRecursively(parentNode, specMap)
}
private fun maybeDetachChild(
@@ -94,14 +91,13 @@
childNode: ShadeNode,
childSpec: NodeSpec?
) {
- val newParentNode = transform(childSpec?.parent) { getNode(it) }
+ val newParentNode = childSpec?.parent?.let { getNode(it) }
if (newParentNode != parentNode) {
val childCompletelyRemoved = newParentNode == null
if (childCompletelyRemoved) {
nodes.remove(childNode.controller)
- views.remove(childNode.controller.view)
}
logger.logDetachingChild(
@@ -115,10 +111,7 @@
}
}
- private fun attachChildren(
- parentNode: ShadeNode,
- specMap: Map<NodeController, NodeSpec>
- ) {
+ private fun attachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
val parentSpec = checkNotNull(specMap[parentNode.controller])
for ((index, childSpec) in parentSpec.children.withIndex()) {
@@ -160,7 +153,6 @@
if (node == null) {
node = ShadeNode(spec.controller)
nodes[node.controller] = node
- views[node.view] = node
}
return node
}
@@ -194,10 +186,9 @@
private class DuplicateNodeException(message: String) : RuntimeException(message)
-private class ShadeNode(
- val controller: NodeController
-) {
- val view = controller.view
+private class ShadeNode(val controller: NodeController) {
+ val view: View
+ get() = controller.view
var parent: ShadeNode? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 34c8044..d96590a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -70,6 +70,8 @@
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
+import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -370,6 +372,10 @@
/** */
@Binds
+ ConversationIconManager bindConversationIconManager(IconManager iconManager);
+
+ /** */
+ @Binds
BindEventManager bindBindEventManagerImpl(BindEventManagerImpl bindEventManagerImpl);
/** */
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 5375ac3..d896541 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
@@ -27,6 +27,7 @@
import android.widget.ImageView
import com.android.internal.statusbar.StatusBarIcon
import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -44,11 +45,14 @@
* TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
* Long-term, it should probably live somewhere in the content inflation pipeline.
*/
+@SysUISingleton
class IconManager @Inject constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
private val iconBuilder: IconBuilder
-) {
+) : ConversationIconManager {
+ private var unimportantConversationKeys: Set<String> = emptySet()
+
fun attach() {
notifCollection.addCollectionListener(entryListener)
}
@@ -63,16 +67,8 @@
}
override fun onRankingApplied() {
- // When the sensitivity changes OR when the isImportantConversation status changes,
- // we need to update the icons
- for (entry in notifCollection.allNotifs) {
- val isImportant = isImportantConversation(entry)
- if (entry.icons.areIconsAvailable &&
- isImportant != entry.icons.isImportantConversation) {
- updateIconsSafe(entry)
- }
- entry.icons.isImportantConversation = isImportant
- }
+ // rankings affect whether a conversation is important, which can change the icons
+ recalculateForImportantConversationChange()
}
}
@@ -80,6 +76,18 @@
entry -> updateIconsSafe(entry)
}
+ private fun recalculateForImportantConversationChange() {
+ for (entry in notifCollection.allNotifs) {
+ val isImportant = isImportantConversation(entry)
+ if (entry.icons.areIconsAvailable &&
+ isImportant != entry.icons.isImportantConversation
+ ) {
+ updateIconsSafe(entry)
+ }
+ entry.icons.isImportantConversation = isImportant
+ }
+ }
+
/**
* Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
* result in [NotificationEntry.getIcons].
@@ -306,8 +314,28 @@
}
private fun isImportantConversation(entry: NotificationEntry): Boolean {
- return entry.ranking.channel != null && entry.ranking.channel.isImportantConversation
+ return entry.ranking.channel != null &&
+ entry.ranking.channel.isImportantConversation &&
+ entry.key !in unimportantConversationKeys
+ }
+
+ override fun setUnimportantConversations(keys: Collection<String>) {
+ val newKeys = keys.toSet()
+ val changed = unimportantConversationKeys != newKeys
+ unimportantConversationKeys = newKeys
+ if (changed) {
+ recalculateForImportantConversationChange()
+ }
}
}
-private const val TAG = "IconManager"
\ No newline at end of file
+private const val TAG = "IconManager"
+
+interface ConversationIconManager {
+ /**
+ * Sets the complete current set of notification keys which should (for the purposes of icon
+ * presentation) be considered unimportant. This tells the icon manager to remove the avatar
+ * of a group from which the priority notification has been removed.
+ */
+ fun setUnimportantConversations(keys: Collection<String>)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 5646545..cff42f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -18,6 +18,8 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
@@ -72,7 +74,7 @@
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val highPriorityProvider: HighPriorityProvider,
- private val statusBarStateController: StatusBarStateController,
+ private val statusBarStateController: SysuiStatusBarStateController,
private val broadcastDispatcher: BroadcastDispatcher,
private val secureSettings: SecureSettings,
private val globalSettings: GlobalSettings
@@ -105,7 +107,8 @@
if (uri == showSilentNotifsUri) {
readShowSilentNotificationSetting()
}
- if (keyguardStateController.isShowing) {
+ if (statusBarStateController.getCurrentOrUpcomingState()
+ == StatusBarState.KEYGUARD) {
notifyStateChanged("Settings $uri changed")
}
}
@@ -131,13 +134,14 @@
// register (maybe) public mode changed callbacks:
statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onStateChanged(state: Int) {
- notifyStateChanged("onStatusBarStateChanged")
+ override fun onUpcomingStateChanged(state: Int) {
+ notifyStateChanged("onStatusBarUpcomingStateChanged")
}
})
broadcastDispatcher.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
- if (keyguardStateController.isShowing) {
+ if (statusBarStateController.getCurrentOrUpcomingState()
+ == StatusBarState.KEYGUARD) {
// maybe public mode changed
notifyStateChanged(intent.action!!)
}
@@ -159,17 +163,28 @@
override fun shouldHideNotification(entry: NotificationEntry): Boolean = when {
// Keyguard state doesn't matter if the keyguard is not showing.
- !keyguardStateController.isShowing -> false
+ statusBarStateController.getCurrentOrUpcomingState() != StatusBarState.KEYGUARD -> false
// Notifications not allowed on the lockscreen, always hide.
!lockscreenUserManager.shouldShowLockscreenNotifications() -> true
// User settings do not allow this notification on the lockscreen, so hide it.
userSettingsDisallowNotification(entry) -> true
+ // if entry is silent, apply custom logic to see if should hide
+ shouldHideIfEntrySilent(entry) -> true
+ else -> false
+ }
+
+ private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when {
+ // Show if high priority (not hidden)
+ highPriorityProvider.isHighPriority(entry) -> false
+ // Ambient notifications are hidden always from lock screen
+ entry.representativeEntry?.isAmbient == true -> true
+ // [Now notification is silent]
+ // Hide regardless of parent priority if user wants silent notifs hidden
+ hideSilentNotificationsOnLockscreen -> true
// Parent priority is high enough to be shown on the lockscreen, do not hide.
- entry.parent?.let(::priorityExceedsLockscreenShowingThreshold) == true -> false
- // Entry priority is high enough to be shown on the lockscreen, do not hide.
- priorityExceedsLockscreenShowingThreshold(entry) -> false
- // Priority is too low, hide.
- else -> true
+ entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
+ // Show when silent notifications are allowed on lockscreen
+ else -> false
}
private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean {
@@ -193,11 +208,6 @@
}
}
- private fun priorityExceedsLockscreenShowingThreshold(entry: ListEntry): Boolean = when {
- hideSilentNotificationsOnLockscreen -> highPriorityProvider.isHighPriority(entry)
- else -> entry.representativeEntry?.ranking?.isAmbient == false
- }
-
private fun readShowSilentNotificationSetting() {
val showSilentNotifs =
secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 36cd173..3ea5e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -70,6 +70,7 @@
import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.SystemBarUtils;
+import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
@@ -1313,7 +1314,10 @@
final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ mAmbientState.getOverExpansion()
- getCurrentOverScrollAmount(false /* top */);
- final float fraction = mAmbientState.getExpansionFraction();
+ float fraction = mAmbientState.getExpansionFraction();
+ if (mAmbientState.isBouncerInTransit()) {
+ fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
+ }
final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
mAmbientState.setStackY(stackY);
if (mOnStackYChanged != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index ef24d77..5c12671 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1022,6 +1022,23 @@
public void onUnlockedChanged() {
logStateToEventlog();
}
+
+ @Override
+ public void onKeyguardGoingAwayChanged() {
+ // The light reveal scrim should always be fully revealed by the time the keyguard
+ // is done going away. Double check that this is true.
+ if (!mKeyguardStateController.isKeyguardGoingAway()) {
+ if (mLightRevealScrim.getRevealAmount() != 1f) {
+ Log.e(TAG, "Keyguard is done going away, but someone left the light reveal "
+ + "scrim at reveal amount: " + mLightRevealScrim.getRevealAmount());
+ }
+
+ // If the auth ripple is still playing, let it finish.
+ if (!mAuthRippleController.isAnimatingLightRevealScrim()) {
+ mLightRevealScrim.setRevealAmount(1f);
+ }
+ }
+ }
});
startKeyguard();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 69beaf56..0b72138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -220,7 +220,7 @@
DejankUtils.postAfterTraversal(mShowRunnable);
}
- mCallback.onBouncerVisiblityChanged(true /* shown */);
+ mKeyguardStateController.notifyBouncerShowing(true /* showing */);
dispatchStartingToShow();
} finally {
Trace.endSection();
@@ -334,7 +334,7 @@
}
mIsScrimmed = false;
mFalsingCollector.onBouncerHidden();
- mCallback.onBouncerVisiblityChanged(false /* shown */);
+ mKeyguardStateController.notifyBouncerShowing(false /* showing */);
cancelShowRunnable();
if (mKeyguardViewController != null) {
mKeyguardViewController.cancelDismissAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 24660b2..faae4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -111,7 +111,6 @@
private final SysuiColorExtractor mColorExtractor;
private final ScreenOffAnimationController mScreenOffAnimationController;
- private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
/**
* Layout params would be aggregated and dispatched all at once if this is > 0.
*
@@ -266,12 +265,6 @@
mScreenBrightnessDoze = value / 255f;
}
- @Override
- public void setFaceAuthDisplayBrightness(float brightness) {
- mFaceAuthDisplayBrightness = brightness;
- apply(mCurrentState);
- }
-
private void setKeyguardDark(boolean dark) {
int vis = mNotificationShadeView.getSystemUiVisibility();
if (dark) {
@@ -340,7 +333,7 @@
}
private void adjustScreenOrientation(State state) {
- if (state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+ if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
} else {
@@ -455,7 +448,9 @@
private void applyWindowLayoutParams() {
if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+ Trace.beginSection("updateViewLayout");
mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+ Trace.endSection();
}
}
@@ -523,7 +518,7 @@
if (state.mForceDozeBrightness) {
mLpChanged.screenBrightness = mScreenBrightnessDoze;
} else {
- mLpChanged.screenBrightness = mFaceAuthDisplayBrightness;
+ mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
}
}
@@ -572,6 +567,10 @@
@Override
public void setPanelVisible(boolean visible) {
+ if (mCurrentState.mPanelVisible == visible
+ && mCurrentState.mNotificationShadeFocusable == visible) {
+ return;
+ }
mCurrentState.mPanelVisible = visible;
mCurrentState.mNotificationShadeFocusable = visible;
apply(mCurrentState);
@@ -626,8 +625,14 @@
@Override
public void setScrimsVisibility(int scrimsVisibility) {
+ if (scrimsVisibility == mCurrentState.mScrimsVisibility) {
+ return;
+ }
+ boolean wasExpanded = isExpanded(mCurrentState);
mCurrentState.mScrimsVisibility = scrimsVisibility;
- apply(mCurrentState);
+ if (wasExpanded != isExpanded(mCurrentState)) {
+ apply(mCurrentState);
+ }
mScrimsVisibilityListener.accept(scrimsVisibility);
}
@@ -687,6 +692,9 @@
@Override
public void setPanelExpanded(boolean isExpanded) {
+ if (mCurrentState.mPanelExpanded == isExpanded) {
+ return;
+ }
mCurrentState.mPanelExpanded = isExpanded;
apply(mCurrentState);
}
@@ -703,6 +711,9 @@
*/
@Override
public void setForceDozeBrightness(boolean forceDozeBrightness) {
+ if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) {
+ return;
+ }
mCurrentState.mForceDozeBrightness = forceDozeBrightness;
apply(mCurrentState);
}
@@ -841,7 +852,6 @@
boolean mLightRevealScrimOpaque;
boolean mForceCollapsed;
boolean mForceDozeBrightness;
- int mFaceAuthDisplayBrightness;
boolean mForceUserActivity;
boolean mLaunchingActivity;
boolean mBackdropShowing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 935f87d..c1d0769 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -175,28 +175,36 @@
.setDuration(duration.toLong())
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
+ .withEndAction {
+ aodUiAnimationPlaying = false
+
+ // Lock the keyguard if it was waiting for the screen off animation to end.
+ keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+
+ // Tell the CentralSurfaces to become keyguard for real - we waited on that
+ // since it is slow and would have caused the animation to jank.
+ mCentralSurfaces.updateIsKeyguard()
+
+ // Run the callback given to us by the KeyguardVisibilityHelper.
+ after.run()
+
+ // Done going to sleep, reset this flag.
+ decidedToAnimateGoingToSleep = null
+
+ // We need to unset the listener. These are persistent for future animators
+ keyguardView.animate().setListener(null)
+ interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+ }
.setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- aodUiAnimationPlaying = false
-
- // Lock the keyguard if it was waiting for the screen off animation to end.
- keyguardViewMediatorLazy.get().maybeHandlePendingLock()
-
- // Tell the CentralSurfaces to become keyguard for real - we waited on that
- // since it is slow and would have caused the animation to jank.
- mCentralSurfaces.updateIsKeyguard()
-
- // Run the callback given to us by the KeyguardVisibilityHelper.
- after.run()
-
- // Done going to sleep, reset this flag.
- decidedToAnimateGoingToSleep = null
- // We need to unset the listener. These are persistent for future animators
- keyguardView.animate().setListener(null)
- interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
- }
-
override fun onAnimationCancel(animation: Animator?) {
+ // If we're cancelled, reset state flags/listeners. The end action above
+ // will not be called, which is what we want since that will finish the
+ // screen off animation and show the lockscreen, which we don't want if we
+ // were cancelled.
+ aodUiAnimationPlaying = false
+ decidedToAnimateGoingToSleep = null
+ keyguardView.animate().setListener(null)
+
interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
index 1e73d59..eb08f37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
@@ -37,6 +37,11 @@
void suppressSensorPrivacyReminders(int sensor, boolean suppress);
+ /**
+ * @return whether lock screen authentication is required to change the toggle state
+ */
+ boolean requiresAuthentication();
+
interface Callback {
void onSensorBlockedChanged(@Sensor int sensor, boolean blocked);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index e4c444d..fffd839 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -37,6 +37,7 @@
private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray();
private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray();
+ private Boolean mRequiresAuthentication;
private final Set<Callback> mCallbacks = new ArraySet<>();
public IndividualSensorPrivacyControllerImpl(
@@ -96,6 +97,11 @@
}
@Override
+ public boolean requiresAuthentication() {
+ return mSensorPrivacyManager.requiresAuthentication();
+ }
+
+ @Override
public void addCallback(@NonNull Callback listener) {
mCallbacks.add(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 233778d..15ee553 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -46,6 +46,11 @@
boolean isShowing();
/**
+ * Whether the bouncer (PIN/password entry) is currently visible.
+ */
+ boolean isBouncerShowing();
+
+ /**
* If swiping up will unlock without asking for a password.
* @see #isUnlocked()
*/
@@ -186,6 +191,8 @@
default void notifyKeyguardDoneFading() {}
/** **/
default void notifyKeyguardState(boolean showing, boolean occluded) {}
+ /** **/
+ default void notifyBouncerShowing(boolean showing) {}
/**
* Updates the keyguard state to reflect that it's in the process of being dismissed, either by
@@ -231,6 +238,11 @@
default void onKeyguardShowingChanged() {}
/**
+ * Called when the bouncer (PIN/password entry) is shown or hidden.
+ */
+ default void onBouncerShowingChanged() {}
+
+ /**
* Triggered when the device was just unlocked and the lock screen is being dismissed.
*/
default void onKeyguardFadingAwayChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index be5da37..77e285d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -63,6 +63,7 @@
private boolean mCanDismissLockScreen;
private boolean mShowing;
+ private boolean mBouncerShowing;
private boolean mSecure;
private boolean mOccluded;
@@ -153,6 +154,11 @@
}
@Override
+ public boolean isBouncerShowing() {
+ return mBouncerShowing;
+ }
+
+ @Override
public boolean isMethodSecure() {
return mSecure;
}
@@ -328,6 +334,15 @@
}
@Override
+ public void notifyBouncerShowing(boolean showing) {
+ if (mBouncerShowing != showing) {
+ mBouncerShowing = showing;
+
+ new ArrayList<>(mCallbacks).forEach(Callback::onBouncerShowingChanged);
+ }
+ }
+
+ @Override
public void notifyPanelFlingEnd() {
mFlingingToDismissKeyguard = false;
mFlingingToDismissKeyguardDuringSwipeGesture = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index f26176c..f8c36dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -86,7 +86,6 @@
private boolean mShouldDisplayAllAccesses;
private boolean mShowSystemAccessesFlag;
private boolean mShowSystemAccessesSetting;
- private boolean mExperimentStarted;
@Inject
public LocationControllerImpl(Context context, AppOpsController appOpsController,
@@ -108,9 +107,6 @@
mShouldDisplayAllAccesses = getAllAccessesSetting();
mShowSystemAccessesFlag = getShowSystemFlag();
mShowSystemAccessesSetting = getShowSystemSetting();
- mExperimentStarted = getExperimentStarted();
- toggleSystemSettingIfExperimentJustStarted();
-
mContentObserver = new ContentObserver(mBackgroundHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -127,15 +123,8 @@
DeviceConfig.NAMESPACE_PRIVACY,
backgroundHandler::post,
properties -> {
- // Update the Device Config flag which controls the experiment to display
- // location accesses.
mShouldDisplayAllAccesses = getAllAccessesSetting();
- // Update the Device Config flag which controls the experiment to display
- // system location accesses.
- mShowSystemAccessesFlag = getShowSystemFlag();
- // Update the local flag for the system accesses experiment and potentially
- // update the behavior based on the flag value.
- toggleSystemSettingIfExperimentJustStarted();
+ mShowSystemAccessesFlag = getShowSystemSetting();
updateActiveLocationRequests();
});
@@ -234,33 +223,6 @@
UserHandle.USER_CURRENT) == 1;
}
- private boolean getExperimentStarted() {
- return mSecureSettings
- .getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 0) == 1;
- }
-
- private void toggleSystemSettingIfExperimentJustStarted() {
- // mShowSystemAccessesFlag indicates whether the Device Config flag is flipped
- // by an experiment. mExperimentStarted is the local device value which indicates the last
- // value the device has seen for the Device Config flag.
- // The local device value is needed to determine that the Device Config flag was just
- // flipped, as the experiment behavior should only happen once after the experiment is
- // enabled.
- if (mShowSystemAccessesFlag && !mExperimentStarted) {
- // If the Device Config flag is enabled, but the local device setting is not then the
- // experiment just started. Update the local flag to match and enable the experiment
- // behavior by flipping the show system setting value.
- mSecureSettings.putInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 1);
- mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1);
- mExperimentStarted = true;
- } else if (!mShowSystemAccessesFlag && mExperimentStarted) {
- // If the Device Config flag is disabled, but the local device flag is enabled then
- // update the local device flag to match.
- mSecureSettings.putInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED, 0);
- mExperimentStarted = false;
- }
- }
-
/**
* Returns true if there currently exist active high power location requests.
*/
@@ -288,6 +250,7 @@
}
boolean hadActiveLocationRequests = mAreActiveLocationRequests;
boolean shouldDisplay = false;
+ boolean showSystem = mShowSystemAccessesFlag || mShowSystemAccessesSetting;
boolean systemAppOp = false;
boolean nonSystemAppOp = false;
boolean isSystemApp;
@@ -305,7 +268,7 @@
nonSystemAppOp = true;
}
- shouldDisplay = mShowSystemAccessesSetting || shouldDisplay || !isSystemApp;
+ shouldDisplay = showSystem || shouldDisplay || !isSystemApp;
}
}
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 3751721..5a33603 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -269,8 +269,12 @@
super.onEnd(animation);
if (animation.getTypeMask() == WindowInsets.Type.ime()) {
mEntry.mRemoteEditImeAnimatingAway = false;
- mEntry.mRemoteEditImeVisible =
- mEditText.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
+ WindowInsets editTextRootWindowInsets = mEditText.getRootWindowInsets();
+ if (editTextRootWindowInsets == null) {
+ Log.w(TAG, "onEnd called on detached view", new Exception());
+ }
+ mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
+ && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
mController.removeRemoteInput(mEntry, mToken);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 5b5dca3..d54de3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -21,9 +21,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.provider.Settings;
-import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import com.android.internal.policy.SystemBarUtils;
@@ -35,6 +33,8 @@
public class Utils {
+ private static Boolean sUseQsMediaPlayer = null;
+
/**
* Allows lambda iteration over a list. It is done in reverse order so it is safe
* to add or remove items during the iteration. Skips over null items.
@@ -81,9 +81,16 @@
* Off by default, but can be disabled by setting to 0
*/
public static boolean useQsMediaPlayer(Context context) {
- int flag = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
- return flag > 0;
+ // TODO(b/192412820): Replace SHOW_MEDIA_ON_QUICK_SETTINGS with compile-time value
+ // Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS can't be toggled at runtime, so simply
+ // cache the first result we fetch and use that going forward. Do this to avoid unnecessary
+ // binder calls which may happen on the critical path.
+ if (sUseQsMediaPlayer == null) {
+ int flag = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
+ sUseQsMediaPlayer = flag > 0;
+ }
+ return sUseQsMediaPlayer;
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index aff94eb..a1e23a20 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -25,7 +25,6 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.concurrency.DelayableExecutor
import org.junit.Before
import org.junit.Test
@@ -63,7 +62,7 @@
@Mock
lateinit var falsingCollector: FalsingCollector
@Mock
- lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ lateinit var keyguardViewController: KeyguardViewController
@Mock
private lateinit var mKeyguardMessageArea: KeyguardMessageArea
@Mock
@@ -92,13 +91,13 @@
mainExecutor,
mContext.resources,
falsingCollector,
- statusBarKeyguardViewManager
+ keyguardViewController
)
}
@Test
fun testFocusWhenBouncerIsShown() {
- Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(true)
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
@@ -106,7 +105,7 @@
@Test
fun testDoNotFocusWhenBouncerIsHidden() {
- Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(false)
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
verify(keyguardPasswordView, never()).requestFocus()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 7b7dfdc..4d33430 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -51,7 +51,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -125,7 +124,7 @@
@Mock
private SessionTracker mSessionTracker;
@Mock
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardViewController mKeyguardViewController;
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -153,7 +152,7 @@
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
SecurityMode.Password, mLockPatternUtils, null,
mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
- null, mock(Resources.class), null, mStatusBarKeyguardViewManager);
+ null, mock(Resources.class), null, mKeyguardViewController);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 57f3617..92c2a1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -29,6 +29,7 @@
@FingerprintSensorProperties.SensorType sensorType: Int =
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
maxEnrollmentsPerUser: Int = 1,
+ halControlsIllumination: Boolean = true,
info: List<ComponentInfoInternal> = listOf(ComponentInfoInternal("a", "b", "c", "d", "e")),
resetLockoutRequiresHardwareAuthToken: Boolean = false
) = FingerprintSensorPropertiesInternal(
@@ -37,6 +38,7 @@
maxEnrollmentsPerUser,
info,
sensorType,
+ halControlsIllumination,
resetLockoutRequiresHardwareAuthToken,
listOf(this)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 102f37c..dec2b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -185,6 +185,7 @@
5 /* maxEnrollmentsPerUser */,
listOf() /* componentInfo */,
FingerprintSensorProperties.TYPE_POWER_BUTTON,
+ true /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
locations
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index a57b011..fc5ccbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -65,6 +65,7 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+private const val HAL_CONTROLS_ILLUMINATION = true
private const val REQUEST_ID = 2L
// Dimensions for the current display resolution.
@@ -129,8 +130,8 @@
statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
- unlockedScreenOffAnimationController, hbmProvider, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator)
+ unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider,
+ REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
index 27755ede..cd646c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
@@ -62,6 +62,7 @@
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
List.of(new SensorLocationInternal("" /* displayId */,
sensorLocationX, sensorLocationY, sensorRadius)));
@@ -127,6 +128,7 @@
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
List.of(new SensorLocationInternal("" /* displayId */,
sensorLocationX, sensorLocationY, sensorRadius)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 744af58..0327cfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -36,6 +36,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.nullable
@@ -43,7 +44,6 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
-private const val DISPLAY_ID = "" // default display id
private const val SENSOR_X = 50
private const val SENSOR_Y = 250
private const val SENSOR_RADIUS = 10
@@ -146,7 +146,7 @@
view.startIllumination(onDone)
val illuminator = withArgCaptor<Runnable> {
- verify(hbmProvider).enableHbm(capture())
+ verify(hbmProvider).enableHbm(anyBoolean(), capture())
}
assertThat(view.isIlluminationRequested).isTrue()
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 2d8c4d5..2abc666 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 com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import junit.framework.Assert.assertEquals
@@ -49,6 +50,8 @@
private lateinit var biometricUnlockController: BiometricUnlockController
@Mock
private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ @Mock
+ private lateinit var statusBarStateController: SysuiStatusBarStateController
private lateinit var remoteAnimationTarget: RemoteAnimationTarget
@@ -57,7 +60,7 @@
MockitoAnnotations.initMocks(this)
keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
- featureFlags, { biometricUnlockController }
+ featureFlags, { biometricUnlockController }, statusBarStateController
)
`when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index be923a6..5ff316e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -413,6 +413,7 @@
/* max enrollments per user */ 5,
/* component info */ new ArrayList<>(),
/* sensorType */ 3,
+ /* halControlsIllumination */ true,
/* resetLockoutRequiresHwToken */ false,
List.of(new SensorLocationInternal("" /* displayId */,
(int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
index 06d45de..a074475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
@@ -27,6 +27,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -136,6 +137,24 @@
}
@Test
+ fun testConnection_calledTwice_oldBrowserDisconnected() {
+ val oldBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+ // When testConnection can connect to the service
+ setupBrowserConnection()
+ resumeBrowser.testConnection()
+
+ // And testConnection is called again
+ val newBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+ resumeBrowser.testConnection()
+
+ // Then we disconnect the old browser
+ verify(oldBrowser).disconnect()
+ }
+
+ @Test
fun testFindRecentMedia_connectionFails_error() {
// When findRecentMedia is called and we cannot connect
setupBrowserFailed()
@@ -169,6 +188,24 @@
}
@Test
+ fun testFindRecentMedia_calledTwice_oldBrowserDisconnected() {
+ val oldBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+ // When findRecentMedia is called and we connect
+ setupBrowserConnection()
+ resumeBrowser.findRecentMedia()
+
+ // And findRecentMedia is called again
+ val newBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+ resumeBrowser.findRecentMedia()
+
+ // Then we disconnect the old browser
+ verify(oldBrowser).disconnect()
+ }
+
+ @Test
fun testFindRecentMedia_noChildren_error() {
// When findRecentMedia is called and we connect, but do not get any results
setupBrowserConnectionNoResults()
@@ -223,6 +260,24 @@
verify(transportControls).play()
}
+ @Test
+ fun testRestart_calledTwice_oldBrowserDisconnected() {
+ val oldBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+ // When restart is called and we connect successfully
+ setupBrowserConnection()
+ resumeBrowser.restart()
+
+ // And restart is called again
+ val newBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+ resumeBrowser.restart()
+
+ // Then we disconnect the old browser
+ verify(oldBrowser).disconnect()
+ }
+
/**
* Helper function to mock a failed connection
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 3c6aa48..7c30398 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -85,6 +85,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.statusbar.CommandQueue;
@@ -187,6 +188,8 @@
private DeadZone mDeadZone;
@Mock
private CentralSurfaces mCentralSurfaces;
+ @Mock
+ private UserContextProvider mUserContextProvider;
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@Rule
@@ -210,6 +213,8 @@
when(mNavigationBarTransitions.getLightTransitionsController())
.thenReturn(mLightBarTransitionsController);
when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true);
+ when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
+ .thenReturn(mContext);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
@@ -458,7 +463,8 @@
mDeadZone,
mDeviceConfigProxyFake,
mNavigationBarTransitions,
- Optional.of(mock(BackAnimation.class))));
+ Optional.of(mock(BackAnimation.class)),
+ mUserContextProvider));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index c88ceac..4f6475f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -90,6 +90,7 @@
private static final String CARD_ID = "card_id";
private static final String LABEL = "QAW";
+ private static final String CARD_DESCRIPTION = "•••• 1234";
private static final Icon CARD_IMAGE =
Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888));
@@ -282,9 +283,7 @@
mTile.handleUpdateState(state, null);
assertEquals(Tile.STATE_ACTIVE, state.state);
- assertEquals(
- "•••• 1234",
- state.secondaryLabel);
+ assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
assertNotNull(state.stateDescription);
assertNotNull(state.sideViewCustomDrawable);
}
@@ -298,11 +297,9 @@
mTile.handleUpdateState(state, null);
assertEquals(Tile.STATE_INACTIVE, state.state);
- assertEquals(
- mContext.getString(R.string.wallet_secondary_label_device_locked),
- state.secondaryLabel);
+ assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
assertNotNull(state.stateDescription);
- assertNull(state.sideViewCustomDrawable);
+ assertNotNull(state.sideViewCustomDrawable);
}
@Test
@@ -314,9 +311,7 @@
mTile.handleUpdateState(state, null);
assertEquals(Tile.STATE_ACTIVE, state.state);
- assertEquals(
- "•••• 1234",
- state.secondaryLabel);
+ assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
assertNotNull(state.stateDescription);
assertNotNull(state.sideViewCustomDrawable);
}
@@ -426,6 +421,6 @@
private WalletCard createWalletCard(Context context) {
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
- return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "•••• 1234", pendingIntent).build();
+ return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index 540d291..1cce3b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -21,10 +21,13 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.Utils
+import com.android.systemui.util.mockito.any
import org.junit.After
import org.junit.Assert.assertFalse
@@ -32,28 +35,33 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
@RunWith(AndroidTestingRunner::class)
@SmallTest
class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
var manager: NotificationSectionsFeatureManager? = null
val proxyFake = DeviceConfigProxyFake()
- var originalQsMediaPlayer: Int = 0
+ private lateinit var staticMockSession: MockitoSession
@Before
public fun setup() {
manager = NotificationSectionsFeatureManager(proxyFake, mContext)
manager!!.clearCache()
- originalQsMediaPlayer = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+ staticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic<Utils>(Utils::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ `when`(Utils.useQsMediaPlayer(any())).thenReturn(false)
Settings.Global.putInt(context.getContentResolver(),
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0)
}
@After
public fun teardown() {
- Settings.Global.putInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsMediaPlayer)
+ staticMockSession.finishMocking()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index b45d78d..4b458f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -88,4 +88,7 @@
return this;
}
+ public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
+ return groupEntry.getRawChildren();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 7692a05..742fcf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -21,17 +21,22 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
@@ -52,8 +57,10 @@
private lateinit var promoter: NotifPromoter
private lateinit var peopleSectioner: NotifSectioner
private lateinit var peopleComparator: NotifComparator
+ private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@Mock private lateinit var pipeline: NotifPipeline
+ @Mock private lateinit var conversationIconManager: ConversationIconManager
@Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
@Mock private lateinit var channel: NotificationChannel
@Mock private lateinit var headerController: NodeController
@@ -66,7 +73,11 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- coordinator = ConversationCoordinator(peopleNotificationIdentifier, headerController)
+ coordinator = ConversationCoordinator(
+ peopleNotificationIdentifier,
+ conversationIconManager,
+ headerController
+ )
whenever(channel.isImportantConversation).thenReturn(true)
coordinator.attach(pipeline)
@@ -75,6 +86,9 @@
promoter = withArgCaptor {
verify(pipeline).addPromoter(capture())
}
+ beforeRenderListListener = withArgCaptor {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
peopleSectioner = coordinator.sectioner
peopleComparator = peopleSectioner.comparator!!
@@ -96,6 +110,25 @@
}
@Test
+ fun testPromotedImportantConversationsMakesSummaryUnimportant() {
+ val altChildA = NotificationEntryBuilder().setTag("A").build()
+ val altChildB = NotificationEntryBuilder().setTag("B").build()
+ val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
+ val groupEntry = GroupEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setSummary(summary)
+ .setChildren(listOf(entry, altChildA, altChildB))
+ .build()
+ assertTrue(promoter.shouldPromoteToTopLevel(entry))
+ assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
+ assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
+ NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY)
+ GroupEntryBuilder.getRawChildren(groupEntry).remove(entry)
+ beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry))
+ verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key)))
+ }
+
+ @Test
fun testInPeopleSection() {
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
.thenReturn(TYPE_PERSON)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index cf99607..3a85972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,11 +22,14 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -55,6 +58,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -90,7 +94,7 @@
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private StatusBarStateController mStatusBarStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
private final FakeSettings mFakeSettings = new FakeSettings();
@@ -178,7 +182,7 @@
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
- callback.onStateChanged(0);
+ callback.onUpcomingStateChanged(0);
verify(listener).accept(anyString());
}
@@ -199,7 +203,7 @@
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED));
verify(listener).accept(anyString());
@@ -207,7 +211,7 @@
@Test
public void notifyListeners_onSettingChange_lockScreenShowNotifs() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -218,7 +222,7 @@
@Test
public void notifyListeners_onSettingChange_lockScreenAllowPrivateNotifs() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -228,8 +232,43 @@
}
@Test
+ public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() {
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+ GroupEntry parent = new GroupEntryBuilder()
+ .setKey("parent")
+ .addChild(mEntry)
+ .setSummary(new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setImportance(IMPORTANCE_LOW)
+ .build())
+ .build();
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setImportance(IMPORTANCE_LOW)
+ .setParent(parent)
+ .build();
+ when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void hideSilentNotificationsPerUserSetting() {
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+ when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
public void notifyListeners_onSettingChange_zenMode() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -240,7 +279,7 @@
@Test
public void notifyListeners_onSettingChange_lockScreenShowSilentNotifs() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -262,7 +301,7 @@
public void keyguardNotShowing() {
// GIVEN the lockscreen isn't showing
setupUnfilteredState(mEntry);
- when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(SHADE);
// THEN don't filter out the entry
assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
@@ -384,8 +423,8 @@
mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true);
- // THEN don't filter out the entry
- assertFalse(
+ // THEN filter out the entry regardless of parent
+ assertTrue(
mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent));
// WHEN its parent doesn't exceed threshold to show on lockscreen
@@ -404,7 +443,7 @@
*/
private void setupUnfilteredState(NotificationEntry entry) {
// keyguard is showing
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
// show notifications on the lockscreen
when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true);
@@ -452,11 +491,11 @@
@BindsInstance NotificationLockscreenUserManager lockscreenUserManager,
@BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor,
@BindsInstance HighPriorityProvider highPriorityProvider,
- @BindsInstance StatusBarStateController statusBarStateController,
+ @BindsInstance SysuiStatusBarStateController statusBarStateController,
@BindsInstance BroadcastDispatcher broadcastDispatcher,
@BindsInstance SecureSettings secureSettings,
@BindsInstance GlobalSettings globalSettings
);
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 4986792..f43c2a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -160,7 +160,7 @@
@Test
public void testShow_notifiesVisibility() {
mBouncer.show(true);
- verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(true));
+ verify(mKeyguardStateController).notifyBouncerShowing(eq(true));
verify(mExpansionCallback).onStartingToShow();
// Not called again when visible
@@ -238,7 +238,7 @@
@Test
public void testHide_notifiesVisibility() {
mBouncer.hide(false);
- verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(false));
+ verify(mKeyguardStateController).notifyBouncerShowing(eq(false));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index ddccd83..c402d2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -26,10 +26,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.IActivityManager;
@@ -103,6 +105,7 @@
mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
+ verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any());
}
@Test
@@ -174,6 +177,14 @@
}
@Test
+ public void setScrimsVisibility_earlyReturn() {
+ clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
+ // Abort early if value didn't change
+ verify(mWindowManager, never()).updateViewLayout(any(), mLayoutParameters.capture());
+ }
+
+ @Test
public void attach_animatingKeyguardAndSurface_wallpaperVisible() {
clearInvocations(mWindowManager);
when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true);
@@ -221,6 +232,8 @@
public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
mNotificationShadeWindowController.setPanelExpanded(true);
clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.setPanelExpanded(true);
+ verifyNoMoreInteractions(mWindowManager);
mNotificationShadeWindowController.setNotificationShadeFocusable(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
@@ -287,6 +300,8 @@
public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
mNotificationShadeWindowController.setForceDozeBrightness(true);
verify(mWindowManager).updateViewLayout(any(), any());
+ mNotificationShadeWindowController.setForceDozeBrightness(true);
+ verifyNoMoreInteractions(mWindowManager);
clearInvocations(mWindowManager);
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
@@ -295,4 +310,17 @@
});
verify(mWindowManager).updateViewLayout(any(), any());
}
+
+ @Test
+ public void bouncerShowing_OrientationNoSensor() {
+ mNotificationShadeWindowController.setKeyguardShowing(true);
+ mNotificationShadeWindowController.setKeyguardOccluded(true);
+ mNotificationShadeWindowController.setBouncerShowing(true);
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+ mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+ verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().screenOrientation)
+ .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 0936b77..0112797 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -117,11 +117,18 @@
val keyguardSpy = spy(keyguardView)
Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
+ val endAction = ArgumentCaptor.forClass(Runnable::class.java)
controller.animateInKeyguard(keyguardSpy, Runnable {})
Mockito.verify(animator).setListener(listener.capture())
- // Verify that the listener is cleared when it ends
- listener.value.onAnimationEnd(null)
+ Mockito.verify(animator).withEndAction(endAction.capture())
+
+ // Verify that the listener is cleared if we cancel it.
+ listener.value.onAnimationCancel(null)
Mockito.verify(animator).setListener(null)
+
+ // Verify that the listener is also cleared if the end action is triggered.
+ endAction.value.run()
+ verify(animator, times(2)).setListener(null)
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
index e9d16a6..64a93cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
@@ -53,7 +53,6 @@
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.never
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.ArgumentMatchers.anyInt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 14e0878..9c7dc6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -365,46 +365,4 @@
// No new callbacks
verify(callback).onLocationSettingsChanged(anyBoolean());
}
-
- @Test
- public void testExperimentFlipsSystemFlag() throws Exception {
- mSecureSettings.putInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0);
- mDeviceConfigProxy.setProperty(
- DeviceConfig.NAMESPACE_PRIVACY,
- SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED,
- "true",
- true);
- // Show system experiment not running
- mDeviceConfigProxy.setProperty(
- DeviceConfig.NAMESPACE_PRIVACY,
- SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
- "false",
- false);
- mTestableLooper.processAllMessages();
-
- // Flip experiment on
- mDeviceConfigProxy.setProperty(
- DeviceConfig.NAMESPACE_PRIVACY,
- SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
- "true",
- true);
- mTestableLooper.processAllMessages();
-
- // Verify settings were flipped
- assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS))
- .isEqualTo(1);
- assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED))
- .isEqualTo(1);
-
- // Flip experiment off
- mDeviceConfigProxy.setProperty(
- DeviceConfig.NAMESPACE_PRIVACY,
- SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
- "false",
- false);
- mTestableLooper.processAllMessages();
-
- assertThat(mSecureSettings.getInt(Settings.Secure.LOCATION_INDICATOR_EXPERIMENT_STARTED))
- .isEqualTo(0);
- }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
index aaea4ec..95b62a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
@@ -49,6 +49,11 @@
}
@Override
+ public boolean isBouncerShowing() {
+ return false;
+ }
+
+ @Override
public boolean canDismissLockScreen() {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 193879e..238a4d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -57,6 +57,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
@@ -70,6 +71,8 @@
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Pair;
+import android.util.SparseArray;
import android.view.View;
import android.view.WindowManager;
@@ -147,6 +150,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@@ -1010,7 +1014,7 @@
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -1027,7 +1031,7 @@
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -1447,6 +1451,69 @@
assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
}
+ @Test
+ public void testSetShouldAutoExpand_notifiesFlagChanged() {
+ mEntryListener.onPendingEntryAdded(mRow);
+
+ assertTrue(mBubbleController.hasBubbles());
+ Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+
+ // Set it to the same thing
+ b.setShouldAutoExpand(false);
+
+ // Verify it doesn't notify
+ verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
+
+ // Set it to something different
+ b.setShouldAutoExpand(true);
+ verify(mBubbleController).onBubbleMetadataFlagChanged(b);
+ }
+
+ @Test
+ public void testUpdateBubble_skipsDndSuppressListNotifs() {
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ mBubbleEntry.getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+ assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
+ }
+
+ @Test
+ public void testOnRankingUpdate_DndSuppressListNotif() {
+ // It's in the stack
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
+
+ // Set current user profile
+ SparseArray<UserInfo> userInfos = new SparseArray<>();
+ userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
+ mock(UserInfo.class));
+ mBubbleController.onCurrentProfilesChanged(userInfos);
+
+ // Send ranking update that the notif is suppressed from the list.
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
+ entryDataByKey.put(mBubbleEntry.getKey(), pair);
+
+ NotificationListenerService.RankingMap rankingMap =
+ mock(NotificationListenerService.RankingMap.class);
+ when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+ mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
+
+ // Should no longer be in the stack
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 02d8691..dff89e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -50,6 +50,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherApps;
+import android.content.pm.UserInfo;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
@@ -59,6 +60,8 @@
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Pair;
+import android.util.SparseArray;
import android.view.View;
import android.view.WindowManager;
@@ -128,6 +131,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@@ -880,7 +884,7 @@
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -897,7 +901,7 @@
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -1267,6 +1271,69 @@
assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
}
+ @Test
+ public void testSetShouldAutoExpand_notifiesFlagChanged() {
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ assertTrue(mBubbleController.hasBubbles());
+ Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+
+ // Set it to the same thing
+ b.setShouldAutoExpand(false);
+
+ // Verify it doesn't notify
+ verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
+
+ // Set it to something different
+ b.setShouldAutoExpand(true);
+ verify(mBubbleController).onBubbleMetadataFlagChanged(b);
+ }
+
+ @Test
+ public void testUpdateBubble_skipsDndSuppressListNotifs() {
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ mBubbleEntry.getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+ assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
+ }
+
+ @Test
+ public void testOnRankingUpdate_DndSuppressListNotif() {
+ // It's in the stack
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
+
+ // Set current user profile
+ SparseArray<UserInfo> userInfos = new SparseArray<>();
+ userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
+ mock(UserInfo.class));
+ mBubbleController.onCurrentProfilesChanged(userInfos);
+
+ // Send ranking update that the notif is suppressed from the list.
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
+ entryDataByKey.put(mBubbleEntry.getKey(), pair);
+
+ NotificationListenerService.RankingMap rankingMap =
+ mock(NotificationListenerService.RankingMap.class);
+ when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+ mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
+
+ // Should no longer be in the stack
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
+ }
+
/**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b4c107c..62bb9f1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -294,6 +294,15 @@
private boolean onCompanionApplicationBindingDiedInternal(
@UserIdInt int userId, @NonNull String packageName) {
+ // Update the current connected devices sets when binderDied, so that application is able
+ // to call notifyDeviceAppeared after re-launch the application.
+ for (AssociationInfo ai :
+ mAssociationStore.getAssociationsForPackage(userId, packageName)) {
+ int id = ai.getId();
+ Slog.i(TAG, "Removing association id: " + id + " for package: "
+ + packageName + " due to binderDied.");
+ mDevicePresenceMonitor.removeDeviceFromMonitoring(id);
+ }
// TODO(b/218613015): implement.
return false;
}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 6371b25..24be1b6 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -206,6 +206,15 @@
}
/**
+ * Remove the current connected devices by associationId.
+ */
+ public void removeDeviceFromMonitoring(int associationId) {
+ mConnectedBtDevices.remove(associationId);
+ mNearbyBleDevices.remove(associationId);
+ mReportedSelfManagedDevices.remove(associationId);
+ }
+
+ /**
* Implements
* {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
*/
@@ -217,9 +226,7 @@
Log.d(TAG, " > association=" + association);
}
- mConnectedBtDevices.remove(id);
- mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
+ removeDeviceFromMonitoring(id);
// Do NOT call mCallback.onDeviceDisappeared()!
// CompanionDeviceManagerService will know that the association is removed, and will do
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 9d4b50b..80182d2 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
@@ -29,11 +30,13 @@
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Display;
+import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,7 +47,11 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Iterator;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
/** Controls virtual input devices, including device lifecycle and event dispatch. */
class InputController {
@@ -72,20 +79,27 @@
@GuardedBy("mLock")
final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
+ private final Handler mHandler;
private final NativeWrapper mNativeWrapper;
private final DisplayManagerInternal mDisplayManagerInternal;
private final InputManagerInternal mInputManagerInternal;
+ private final DeviceCreationThreadVerifier mThreadVerifier;
- InputController(@NonNull Object lock) {
- this(lock, new NativeWrapper());
+ InputController(@NonNull Object lock, @NonNull Handler handler) {
+ this(lock, new NativeWrapper(), handler,
+ // Verify that virtual devices are not created on the handler thread.
+ () -> !handler.getLooper().isCurrentThread());
}
@VisibleForTesting
- InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
+ InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper,
+ @NonNull Handler handler, @NonNull DeviceCreationThreadVerifier threadVerifier) {
mLock = lock;
+ mHandler = handler;
mNativeWrapper = nativeWrapper;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ mThreadVerifier = threadVerifier;
}
void close() {
@@ -108,23 +122,13 @@
@NonNull IBinder deviceToken,
int displayId) {
final String phys = createPhys(PHYS_TYPE_KEYBOARD);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating keyboard: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual keyboard", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
+ productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual keyboard device '" + deviceName + "'.", e);
}
}
@@ -134,25 +138,15 @@
@NonNull IBinder deviceToken,
int displayId) {
final String phys = createPhys(PHYS_TYPE_MOUSE);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating mouse: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
- mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual mouse", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
+ deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual mouse device: '" + deviceName + "'.", e);
}
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
}
void createTouchscreen(@NonNull String deviceName,
@@ -162,24 +156,14 @@
int displayId,
@NonNull Point screenSize) {
final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
- screenSize.y, screenSize.x);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating touchscreen: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual touchscreen", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
+ productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ phys, screenSize.y, screenSize.x));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
}
}
@@ -510,4 +494,133 @@
unregisterInputDevice(mDeviceToken);
}
}
+
+ /** A helper class used to wait for an input device to be registered. */
+ private class WaitForDevice implements AutoCloseable {
+ private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
+ private final InputManager.InputDeviceListener mListener;
+
+ WaitForDevice(String deviceName, int vendorId, int productId) {
+ mListener = new InputManager.InputDeviceListener() {
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ final InputDevice device = InputManager.getInstance().getInputDevice(
+ deviceId);
+ Objects.requireNonNull(device, "Newly added input device was null.");
+ if (!device.getName().equals(deviceName)) {
+ return;
+ }
+ final InputDeviceIdentifier id = device.getIdentifier();
+ if (id.getVendorId() != vendorId || id.getProductId() != productId) {
+ return;
+ }
+ mDeviceAddedLatch.countDown();
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+
+ }
+ };
+ InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
+ }
+
+ /** Note: This must not be called from {@link #mHandler}'s thread. */
+ void waitForDeviceCreation() throws DeviceCreationException {
+ try {
+ if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
+ throw new DeviceCreationException(
+ "Timed out waiting for virtual device to be created.");
+ }
+ } catch (InterruptedException e) {
+ throw new DeviceCreationException(
+ "Interrupted while waiting for virtual device to be created.", e);
+ }
+ }
+
+ @Override
+ public void close() {
+ InputManager.getInstance().unregisterInputDeviceListener(mListener);
+ }
+ }
+
+ /** An internal exception that is thrown to indicate an error when opening a virtual device. */
+ private static class DeviceCreationException extends Exception {
+ DeviceCreationException(String message) {
+ super(message);
+ }
+ DeviceCreationException(String message, Exception cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Creates a virtual input device synchronously, and waits for the notification that the device
+ * was added.
+ *
+ * Note: Input device creation is expected to happen on a binder thread, and the calling thread
+ * will be blocked until the input device creation is successful. This should not be called on
+ * the handler's thread.
+ *
+ * @throws DeviceCreationException Throws this exception if anything unexpected happens in the
+ * process of creating the device. This method will take care
+ * to restore the state of the system in the event of any
+ * unexpected behavior.
+ */
+ private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
+ int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
+ Supplier<Integer> deviceOpener)
+ throws DeviceCreationException {
+ if (!mThreadVerifier.isValidThread()) {
+ throw new IllegalStateException(
+ "Virtual device creation should happen on an auxiliary thread (e.g. binder "
+ + "thread) and not from the handler's thread.");
+ }
+
+ final int fd;
+ final BinderDeathRecipient binderDeathRecipient;
+
+ setUniqueIdAssociation(displayId, phys);
+ try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
+ fd = deviceOpener.get();
+ if (fd < 0) {
+ throw new DeviceCreationException(
+ "A native error occurred when creating touchscreen: " + -fd);
+ }
+ // The fd is valid from here, so ensure that all failures close the fd after this point.
+ try {
+ waiter.waitForDeviceCreation();
+
+ binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+ try {
+ deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException e) {
+ throw new DeviceCreationException(
+ "Client died before virtual device could be created.", e);
+ }
+ } catch (DeviceCreationException e) {
+ mNativeWrapper.closeUinput(fd);
+ throw e;
+ }
+ } catch (DeviceCreationException e) {
+ InputManager.getInstance().removeUniqueIdAssociation(phys);
+ throw e;
+ }
+
+ synchronized (mLock) {
+ mInputDeviceDescriptors.put(deviceToken,
+ new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
+ }
+ }
+
+ @VisibleForTesting
+ interface DeviceCreationThreadVerifier {
+ /** Returns true if the calling thread is a valid thread for device creation. */
+ boolean isValidThread();
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index de14ef6..9802b97 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -166,7 +166,8 @@
mAppToken = token;
mParams = params;
if (inputController == null) {
- mInputController = new InputController(mVirtualDeviceLock);
+ mInputController = new InputController(
+ mVirtualDeviceLock, context.getMainThreadHandler());
} else {
mInputController = inputController;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 8f37823..bc40170 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3055,19 +3055,7 @@
return true;
}
- if (packageName == null) {
- return false;
- }
-
- final int packageUid = mPmInternal.getPackageUid(packageName,
- PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callerUid));
-
- if (DEBUG_OBB) {
- Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
- packageUid + ", callerUid = " + callerUid);
- }
-
- return callerUid == packageUid;
+ return mPmInternal.isSameApp(packageName, callerUid, UserHandle.getUserId(callerUid));
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2ceb00d..dea8cc0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -89,7 +89,6 @@
import static android.os.Process.myPid;
import static android.os.Process.myUid;
import static android.os.Process.readProcFile;
-import static android.os.Process.removeAllProcessGroups;
import static android.os.Process.sendSignal;
import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
@@ -2440,8 +2439,6 @@
}
private void start() {
- removeAllProcessGroups();
-
mBatteryStatsService.publish();
mAppOpsService.publish();
mProcessStats.publish();
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index f7abb11..6f5d87b 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -265,6 +265,20 @@
*/
private int[] mDeviceIdleExceptIdleAllowlist = new int[0]; // No lock is needed.
+ /**
+ * The pre-configured system app-ids in the power-save allow list.
+ *
+ * @see #mDeviceIdleAllowlist.
+ */
+ private final ArraySet<Integer> mSystemDeviceIdleAllowlist = new ArraySet<>();
+
+ /**
+ * The pre-configured system app-ids in the power-save allow list, except-idle.
+ *
+ * @see #mDeviceIdleExceptIdleAllowlist.
+ */
+ private final ArraySet<Integer> mSystemDeviceIdleExceptIdleAllowlist = new ArraySet<>();
+
private final Object mLock = new Object();
private final Object mSettingsLock = new Object();
private final Injector mInjector;
@@ -1511,14 +1525,33 @@
}
private void initBgRestrictionExemptioFromSysConfig() {
- mBgRestrictionExemptioFromSysConfig =
- SystemConfig.getInstance().getBgRestrictionExemption();
+ final SystemConfig sysConfig = SystemConfig.getInstance();
+ mBgRestrictionExemptioFromSysConfig = sysConfig.getBgRestrictionExemption();
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig;
for (int i = exemptedPkgs.size() - 1; i >= 0; i--) {
Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i));
}
}
+ loadAppIdsFromPackageList(sysConfig.getAllowInPowerSaveExceptIdle(),
+ mSystemDeviceIdleExceptIdleAllowlist);
+ loadAppIdsFromPackageList(sysConfig.getAllowInPowerSave(), mSystemDeviceIdleAllowlist);
+ }
+
+ private void loadAppIdsFromPackageList(ArraySet<String> packages, ArraySet<Integer> apps) {
+ final PackageManager pm = mInjector.getPackageManager();
+ for (int i = packages.size() - 1; i >= 0; i--) {
+ final String pkg = packages.valueAt(i);
+ try {
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ if (ai == null) {
+ continue;
+ }
+ apps.add(UserHandle.getAppId(ai.uid));
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
}
private boolean isExemptedFromSysConfig(String packageName) {
@@ -2685,6 +2718,13 @@
|| Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, appId) >= 0;
}
+ boolean isOnSystemDeviceIdleAllowlist(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+
+ return mSystemDeviceIdleAllowlist.contains(appId)
+ || mSystemDeviceIdleExceptIdleAllowlist.contains(appId);
+ }
+
void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) {
mDeviceIdleAllowlist = allAppids;
mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
@@ -2703,6 +2743,9 @@
if (UserHandle.isCore(uid)) {
return REASON_SYSTEM_UID;
}
+ if (isOnSystemDeviceIdleAllowlist(uid)) {
+ return REASON_SYSTEM_ALLOW_LISTED;
+ }
if (isOnDeviceIdleAllowlist(uid)) {
return REASON_ALLOWLISTED_PACKAGE;
}
@@ -2748,7 +2791,7 @@
} else if (isExemptedFromSysConfig(pkg)) {
return REASON_SYSTEM_ALLOW_LISTED;
} else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
- return REASON_ALLOWLISTED_PACKAGE;
+ return REASON_SYSTEM_ALLOW_LISTED;
} else if (pm.isPackageStateProtected(pkg, userId)) {
return REASON_DPO_PROTECTED_APP;
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index a172018..e49497e 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -479,19 +479,33 @@
@GuardedBy("mProcLock")
void compactAppSome(ProcessRecord app, boolean force) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, " compactAppSome requested for " + app.processName + " force: " + force);
+ compactApp(app, force, "some");
+ }
+
+ // This method returns true only if requirements are met. Note, that requirements are different
+ // from throttles applied at the time a compaction is trying to be executed in the sense that
+ // these are not subject to change dependent on time or memory as throttles usually do.
+ @GuardedBy("mProcLock")
+ boolean meetsCompactionRequirements(ProcessRecord proc) {
+ if (mAm.mInternal.isPendingTopUid(proc.uid)) {
+ // In case the OOM Adjust has not yet been propagated we see if this is
+ // pending on becoming top app in which case we should not compact.
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "Skip compaction since UID is active for " + proc.processName);
+ }
+ return false;
}
- if (force || !app.mOptRecord.hasPendingCompact()) {
- Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppSome " + app.processName != null ? app.processName : "");
- app.mOptRecord.setHasPendingCompact(true);
- app.mOptRecord.setForceCompact(force);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+
+ if (proc.mState.hasForegroundActivities()) {
+ if (DEBUG_COMPACTION) {
+ Slog.e(TAG_AM,
+ "Skip compaction as process " + proc.processName
+ + " has foreground activities");
+ }
+ return false;
}
+
+ return true;
}
@GuardedBy("mProcLock")
@@ -508,19 +522,7 @@
// Apply OOM adj score throttle for Full App Compaction.
if (force || oomAdjEnteredCached) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
- if (!app.mOptRecord.hasPendingCompact()) {
- Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppFull " + app.processName != null ? app.processName : "");
- app.mOptRecord.setHasPendingCompact(true);
- app.mOptRecord.setForceCompact(force);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
- } else if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM,
- " compactAppFull Skipped for " + app.processName
- + " since it has a pending compact");
- }
+ compactApp(app, force, "Full");
} else {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "Skipping full compaction for " + app.processName
@@ -533,15 +535,34 @@
@GuardedBy("mProcLock")
void compactAppPersistent(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
- if (!app.mOptRecord.hasPendingCompact()) {
+ compactApp(app, false, "Persistent");
+ }
+
+ @GuardedBy("mProcLock")
+ boolean compactApp(ProcessRecord app, boolean force, String compactRequestType) {
+ if (!app.mOptRecord.hasPendingCompact() && meetsCompactionRequirements(app)) {
+ final String processName = (app.processName != null ? app.processName : "");
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "compactApp " + compactRequestType + " " + processName);
+ }
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppPersistent " + app.processName != null ? app.processName : "");
+ "compactApp " + compactRequestType + " " + processName);
app.mOptRecord.setHasPendingCompact(true);
+ app.mOptRecord.setForceCompact(force);
mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+ return true;
}
+
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ " compactApp Skipped for " + app.processName
+ + " pendingCompact= " + app.mOptRecord.hasPendingCompact()
+ + " meetsCompactionRequirements=" + meetsCompactionRequirements(app)
+ + ". Requested compact: " + app.mOptRecord.getReqCompactAction());
+ }
+ return false;
}
@GuardedBy("mProcLock")
@@ -553,15 +574,7 @@
@GuardedBy("mProcLock")
void compactAppBfgs(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
- if (!app.mOptRecord.hasPendingCompact()) {
- Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppBfgs " + app.processName != null ? app.processName : "");
- app.mOptRecord.setHasPendingCompact(true);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
- }
+ compactApp(app, false, " Bfgs");
}
@GuardedBy("mProcLock")
@@ -572,6 +585,9 @@
void compactAllSystem() {
if (useCompaction()) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "compactAllSystem");
+ }
Trace.instantForTrack(
Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, "compactAllSystem");
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
@@ -1175,13 +1191,13 @@
cancelCompaction();
}
- // Perform a minor compaction when a perceptible app becomes the prev/home app
- // Perform a major compaction when any app enters cached
if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
&& (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
+ // Perform a minor compaction when a perceptible app becomes the prev/home app
compactAppSome(app, false);
} else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ
&& newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
+ // Perform a major compaction when any app enters cached
compactAppFull(app, false);
}
}
@@ -1241,12 +1257,6 @@
private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc, int action) {
final String name = proc.processName;
- if (mAm.mInternal.isPendingTopUid(proc.uid)) {
- // In case the OOM Adjust has not yet been propagated we see if this is
- // pending on becoming top app in which case we should not compact.
- Slog.e(TAG_AM, "Skip compaction since UID is active for " + name);
- return true;
- }
// don't compact if the process has returned to perceptible
// and this is only a cached/home/prev compaction
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index b6757c8..17fff91 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -72,7 +72,7 @@
Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
Context.BIND_FOREGROUND_SERVICE,
Context.BIND_TREAT_LIKE_ACTIVITY,
- Context.BIND_VISIBLE,
+ Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
Context.BIND_SHOWING_UI,
Context.BIND_NOT_VISIBLE,
Context.BIND_NOT_PERCEPTIBLE,
@@ -225,8 +225,8 @@
if ((flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0) {
sb.append("SLTA ");
}
- if ((flags&Context.BIND_VISIBLE) != 0) {
- sb.append("VIS ");
+ if ((flags & Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) != 0) {
+ sb.append("VFGS ");
}
if ((flags&Context.BIND_SHOWING_UI) != 0) {
sb.append("UI ");
diff --git a/services/core/java/com/android/server/am/ErrorDialogController.java b/services/core/java/com/android/server/am/ErrorDialogController.java
index a4e8f92..82f35ad 100644
--- a/services/core/java/com/android/server/am/ErrorDialogController.java
+++ b/services/core/java/com/android/server/am/ErrorDialogController.java
@@ -144,7 +144,8 @@
if (mWaitDialog == null) {
return;
}
- mWaitDialog.dismiss();
+ final BaseErrorDialog dialog = mWaitDialog;
+ mService.mUiHandler.post(dialog::dismiss);
mWaitDialog = null;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 4d10574..e7fcc59 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -39,6 +39,7 @@
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
@@ -2061,6 +2062,10 @@
newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
newAdj = clientAdj;
+ } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
+ && clientAdj <= ProcessList.VISIBLE_APP_ADJ
+ && adj > ProcessList.VISIBLE_APP_ADJ) {
+ newAdj = ProcessList.VISIBLE_APP_ADJ;
} else {
if (adj > ProcessList.VISIBLE_APP_ADJ) {
// TODO: Is this too limiting for apps bound from TOP?
@@ -2097,7 +2102,9 @@
// processes). These should not bring the current process
// into the top state, since they are not on top. Instead
// give them the best bound state after that.
- if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
+ if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
+ clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else if (mService.mWakefulness.get()
== PowerManagerInternal.WAKEFULNESS_AWAKE
@@ -2556,20 +2563,21 @@
// reminder: here, setAdj is previous state, curAdj is upcoming state
if (state.getCurAdj() != state.getSetAdj()) {
mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
- } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
- && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
- && !state.isRunningRemoteAnimation()
- // Because these can fire independent of oom_adj/procstate changes, we need
- // to throttle the actual dispatch of these requests in addition to the
- // processing of the requests. As a result, there is throttling both here
- // and in CachedAppOptimizer.
- && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
- mCachedAppOptimizer.compactAppPersistent(app);
- } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
- && state.getCurProcState()
- == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
- mCachedAppOptimizer.compactAppBfgs(app);
+ } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ // See if we can compact persistent and bfgs services now that screen is off
+ if (state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
+ && !state.isRunningRemoteAnimation()
+ // Because these can fire independent of oom_adj/procstate changes, we need
+ // to throttle the actual dispatch of these requests in addition to the
+ // processing of the requests. As a result, there is throttling both here
+ // and in CachedAppOptimizer.
+ && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+ mCachedAppOptimizer.compactAppPersistent(app);
+ } else if (state.getCurProcState()
+ == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+ mCachedAppOptimizer.compactAppBfgs(app);
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1f3fbff..aed63ce 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9135,6 +9135,8 @@
if (timeOutMs <= 0 || usages.length == 0) {
throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
}
+ Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs
+ + " usages:" + usages);
if (mDeviceBroker.isDeviceConnected(device)) {
// not throwing an exception as there could be a race between a connection (server-side,
@@ -9178,7 +9180,7 @@
Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
return;
}
- if (!device.equals(mMutingExpectedDevice)) {
+ if (!device.equalTypeAddress(mMutingExpectedDevice)) {
Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
+ "] but expected device is" + mMutingExpectedDevice);
throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 74c8999..565783f 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -1158,6 +1158,8 @@
//==========================================================================================
void muteAwaitConnection(@NonNull int[] usagesToMute,
@NonNull AudioDeviceAttributes dev, long timeOutMs) {
+ sEventLogger.loglogi(
+ "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs, TAG);
synchronized (mPlayerLock) {
mutePlayersExpectingDevice(usagesToMute);
// schedule timeout (remove previously scheduled first)
@@ -1169,6 +1171,7 @@
}
void cancelMuteAwaitConnection() {
+ sEventLogger.loglogi("cancelMuteAwaitConnection()", TAG);
synchronized (mPlayerLock) {
// cancel scheduled timeout, ignore device, only one expected device at a time
mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b5c8cd1..bc550d3 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -805,9 +805,10 @@
if (isUdfps && udfpsProps.length == 3) {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
- componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken,
- List.of(new SensorLocationInternal("" /* display */,
- udfpsProps[0], udfpsProps[1], udfpsProps[2])));
+ componentInfo, sensorType, true /* halControlsIllumination */,
+ resetLockoutRequiresHardwareAuthToken,
+ List.of(new SensorLocationInternal("" /* display */, udfpsProps[0],
+ udfpsProps[1], udfpsProps[2])));
} else {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 998a8e1..a600f08 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -176,6 +176,7 @@
prop.commonProps.maxEnrollmentsPerUser,
componentInfo,
prop.sensorType,
+ prop.halControlsIllumination,
true /* resetLockoutRequiresHardwareAuthToken */,
!workaroundLocations.isEmpty() ? workaroundLocations :
Arrays.stream(prop.sensorLocations).map(location ->
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 485a674..bea0f4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -400,7 +400,7 @@
.getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* halControlsIllumination */,
resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
mMockHalResultController = controller;
mUserHasTrust = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f3d41c40..2cf11c6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1440,16 +1440,6 @@
DisplayDevice device =
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
if (device != null) {
- final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
- if (display != null) {
- final int displayId = display.getDisplayIdLocked();
- if (mDisplayWindowPolicyControllers.contains(displayId)) {
- Pair<IVirtualDevice, DisplayWindowPolicyController> pair =
- mDisplayWindowPolicyControllers.removeReturnOld(displayId);
- getLocalService(VirtualDeviceManagerInternal.class)
- .onVirtualDisplayRemoved(pair.first, displayId);
- }
- }
// TODO: multi-display - handle virtual displays the same as other display adapters.
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
@@ -1609,6 +1599,17 @@
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
scheduleTraversalLocked(false);
+
+ if (mDisplayWindowPolicyControllers.contains(displayId)) {
+ final IVirtualDevice virtualDevice = mDisplayWindowPolicyControllers.removeReturnOld(
+ displayId).first;
+ if (virtualDevice != null) {
+ mHandler.post(() -> {
+ getLocalService(VirtualDeviceManagerInternal.class)
+ .onVirtualDisplayRemoved(virtualDevice, displayId);
+ });
+ }
+ }
}
private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8ab0b93..5ae3f33 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -164,6 +164,7 @@
private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
+ private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
@@ -276,11 +277,24 @@
@GuardedBy("mAssociationLock")
private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+ // Guards per-display input properties and properties relating to the mouse pointer.
+ // Threads can wait on this lock to be notified the next time the display on which the mouse
+ // pointer is shown has changed.
private final Object mAdditionalDisplayInputPropertiesLock = new Object();
- // Forces the MouseCursorController to target a specific display id.
+ // Forces the PointerController to target a specific display id.
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
+
+ // PointerController is the source of truth of the pointer display. This is the value of the
+ // latest pointer display id reported by PointerController.
+ @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+ private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY;
+ // This is the latest display id that IMS has requested PointerController to use. If there are
+ // no devices that can control the pointer, PointerController may end up disregarding this
+ // value.
+ @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+ private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY;
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties =
new SparseArray<>();
@@ -289,7 +303,6 @@
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private PointerIcon mIcon;
-
// Holds all the registered gesture monitors that are implemented as spy windows. The spy
// windows are mapped by their InputChannel tokens.
@GuardedBy("mInputMonitors")
@@ -383,6 +396,10 @@
NativeInputManagerService getNativeService(InputManagerService service) {
return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
}
+
+ void registerLocalService(InputManagerInternal localService) {
+ LocalServices.addService(InputManagerInternal.class, localService);
+ }
}
public InputManagerService(Context context) {
@@ -391,11 +408,14 @@
@VisibleForTesting
InputManagerService(Injector injector) {
+ // The static association map is accessed by both java and native code, so it must be
+ // initialized before initializing the native service.
+ mStaticAssociations = loadStaticInputPortAssociations();
+
mContext = injector.getContext();
mHandler = new InputManagerHandler(injector.getLooper());
mNative = injector.getNativeService(this);
- mStaticAssociations = loadStaticInputPortAssociations();
mUseDevInputEventForAudioJack =
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
@@ -406,7 +426,7 @@
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
- LocalServices.addService(InputManagerInternal.class, new LocalService());
+ injector.registerLocalService(new LocalService());
}
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
@@ -556,6 +576,8 @@
vArray[i] = viewports.get(i);
}
mNative.setDisplayViewports(vArray);
+ // Always attempt to update the pointer display when viewports change.
+ updatePointerDisplayId();
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
final AdditionalDisplayInputProperties properties =
@@ -1961,10 +1983,43 @@
return result;
}
- private void setVirtualMousePointerDisplayId(int displayId) {
+ /**
+ * Update the display on which the mouse pointer is shown.
+ * If there is an overridden display for the mouse pointer, use that. Otherwise, query
+ * WindowManager for the pointer display.
+ *
+ * @return true if the pointer displayId changed, false otherwise.
+ */
+ private boolean updatePointerDisplayId() {
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ final int pointerDisplayId = mOverriddenPointerDisplayId != Display.INVALID_DISPLAY
+ ? mOverriddenPointerDisplayId : mWindowManagerCallbacks.getPointerDisplayId();
+ if (mRequestedPointerDisplayId == pointerDisplayId) {
+ return false;
+ }
+ mRequestedPointerDisplayId = pointerDisplayId;
+ mNative.setPointerDisplayId(pointerDisplayId);
+ return true;
+ }
+ }
+
+ private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ mAcknowledgedPointerDisplayId = args.mPointerDisplayId;
+ // Notify waiting threads that the display of the mouse pointer has changed.
+ mAdditionalDisplayInputPropertiesLock.notifyAll();
+ }
+ mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
+ args.mPointerDisplayId, args.mXPosition, args.mYPosition);
+ }
+
+ private boolean setVirtualMousePointerDisplayIdBlocking(int displayId) {
+ // Indicates whether this request is for removing the override.
+ final boolean removingOverride = displayId == Display.INVALID_DISPLAY;
+
synchronized (mAdditionalDisplayInputPropertiesLock) {
mOverriddenPointerDisplayId = displayId;
- if (displayId != Display.INVALID_DISPLAY) {
+ if (!removingOverride) {
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.get(displayId);
if (properties != null) {
@@ -1972,9 +2027,30 @@
updatePointerIconVisibleLocked(properties.pointerIconVisible);
}
}
+ if (!updatePointerDisplayId() && mAcknowledgedPointerDisplayId == displayId) {
+ // The requested pointer display is already set.
+ return true;
+ }
+ if (removingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) {
+ // The pointer display override is being removed, but the current pointer display
+ // is already invalid. This can happen when the PointerController is destroyed as a
+ // result of the removal of all input devices that can control the pointer.
+ return true;
+ }
+ try {
+ // The pointer display changed, so wait until the change has propagated.
+ mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/);
+ } catch (InterruptedException ignored) {
+ }
+ // This request succeeds in two cases:
+ // - This request was to remove the override, in which case the new pointer display
+ // could be anything that WM has set.
+ // - We are setting a new override, in which case the request only succeeds if the
+ // reported new displayId is the one we requested. This check ensures that if two
+ // competing overrides are requested in succession, the caller can be notified if one
+ // of them fails.
+ return removingOverride || mAcknowledgedPointerDisplayId == displayId;
}
- // TODO(b/215597605): trigger MousePositionTracker update
- mNative.notifyPointerDisplayIdChanged();
}
private int getVirtualMousePointerDisplayId() {
@@ -3156,18 +3232,6 @@
// Native callback.
@SuppressWarnings("unused")
- private int getPointerDisplayId() {
- synchronized (mAdditionalDisplayInputPropertiesLock) {
- // Prefer the override to all other displays.
- if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
- return mOverriddenPointerDisplayId;
- }
- }
- return mWindowManagerCallbacks.getPointerDisplayId();
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
if (!mSystemReady) {
return null;
@@ -3206,6 +3270,26 @@
return null;
}
+ private static class PointerDisplayIdChangedArgs {
+ final int mPointerDisplayId;
+ final float mXPosition;
+ final float mYPosition;
+ PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
+ mPointerDisplayId = pointerDisplayId;
+ mXPosition = xPosition;
+ mYPosition = yPosition;
+ }
+ }
+
+ // Native callback.
+ @SuppressWarnings("unused")
+ @VisibleForTesting
+ void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
+ mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
+ new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
+ yPosition)).sendToTarget();
+ }
+
/**
* Callback interface implemented by the Window Manager.
*/
@@ -3329,6 +3413,14 @@
*/
@Nullable
SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
+
+ /**
+ * Notify WindowManagerService when the display of the mouse pointer changes.
+ * @param displayId The display on which the mouse pointer is shown.
+ * @param x The x coordinate of the mouse pointer.
+ * @param y The y coordinate of the mouse pointer.
+ */
+ void notifyPointerDisplayIdChanged(int displayId, float x, float y);
}
/**
@@ -3381,6 +3473,9 @@
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ case MSG_POINTER_DISPLAY_ID_CHANGED:
+ handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
+ break;
}
}
}
@@ -3631,8 +3726,9 @@
}
@Override
- public void setVirtualMousePointerDisplayId(int pointerDisplayId) {
- InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId);
+ public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) {
+ return InputManagerService.this
+ .setVirtualMousePointerDisplayIdBlocking(pointerDisplayId);
}
@Override
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 2169155..81882d2 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -176,6 +176,9 @@
void cancelCurrentTouch();
+ /** Set the displayId on which the mouse cursor should be shown. */
+ void setPointerDisplayId(int displayId);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -388,5 +391,8 @@
@Override
public native void cancelCurrentTouch();
+
+ @Override
+ public native void setPointerDisplayId(int displayId);
}
}
diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
index 79088d0..f9a8407 100644
--- a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
+++ b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java
@@ -16,26 +16,28 @@
package com.android.server.logcat;
+import android.annotation.StyleRes;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.logcat.ILogcatManagerService;
import android.util.Slog;
+import android.view.ContextThemeWrapper;
import android.view.InflateException;
+import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.server.LocalServices;
/**
* Dialog responsible for obtaining user consent per-use log access
@@ -43,61 +45,61 @@
public class LogAccessDialogActivity extends Activity implements
View.OnClickListener {
private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
- private Context mContext;
- private final ILogcatManagerService mLogcatManagerService =
- ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat"));
+ private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
+ private static final int MSG_DISMISS_DIALOG = 0;
+
+ private final LogcatManagerService.LogcatManagerServiceInternal mLogcatManagerInternal =
+ LocalServices.getService(LogcatManagerService.LogcatManagerServiceInternal.class);
private String mPackageName;
-
private int mUid;
- private int mGid;
- private int mPid;
- private int mFd;
+
private String mAlertTitle;
private AlertDialog.Builder mAlertDialog;
private AlertDialog mAlert;
private View mAlertView;
- private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
- private static final int MSG_DISMISS_DIALOG = 0;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- try {
- mContext = this;
-
- // retrieve Intent extra information
- Intent intent = getIntent();
- getIntentInfo(intent);
-
- // retrieve the title string from passed intent extra
- mAlertTitle = getTitleString(mContext, mPackageName, mUid);
-
- // creaet View
- mAlertView = createView();
-
- // create AlertDialog
- mAlertDialog = new AlertDialog.Builder(this);
- mAlertDialog.setView(mAlertView);
-
- // show Alert
- mAlert = mAlertDialog.create();
- mAlert.show();
-
- // set Alert Timeout
- mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
-
- } catch (Exception e) {
- try {
- Slog.e(TAG, "onCreate failed, declining the logd access", e);
- mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
- } catch (RemoteException ex) {
- Slog.e(TAG, "Fails to call remote functions", ex);
- }
+ // retrieve Intent extra information
+ if (!readIntentInfo(getIntent())) {
+ Slog.e(TAG, "Invalid Intent extras, finishing");
+ finish();
+ return;
}
+
+ // retrieve the title string from passed intent extra
+ try {
+ mAlertTitle = getTitleString(this, mPackageName, mUid);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Unable to fetch label of package " + mPackageName, e);
+ declineLogAccess();
+ finish();
+ return;
+ }
+
+ // create View
+ boolean isDarkTheme = (getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+ int themeId = isDarkTheme ? android.R.style.Theme_DeviceDefault_Dialog_Alert :
+ android.R.style.Theme_DeviceDefault_Light_Dialog_Alert;
+ mAlertView = createView(themeId);
+
+ // create AlertDialog
+ mAlertDialog = new AlertDialog.Builder(this, themeId);
+ mAlertDialog.setView(mAlertView);
+ mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
+ mAlertDialog.setOnDismissListener(dialog -> finish());
+
+ // show Alert
+ mAlert = mAlertDialog.create();
+ mAlert.show();
+
+ // set Alert Timeout
+ mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
}
@Override
@@ -109,21 +111,26 @@
mAlert = null;
}
- private void getIntentInfo(Intent intent) throws Exception {
-
+ private boolean readIntentInfo(Intent intent) {
if (intent == null) {
- throw new NullPointerException("Intent is null");
+ Slog.e(TAG, "Intent is null");
+ return false;
}
mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
if (mPackageName == null || mPackageName.length() == 0) {
- throw new NullPointerException("Package Name is null");
+ Slog.e(TAG, "Missing package name extra");
+ return false;
}
- mUid = intent.getIntExtra("com.android.server.logcat.uid", 0);
- mGid = intent.getIntExtra("com.android.server.logcat.gid", 0);
- mPid = intent.getIntExtra("com.android.server.logcat.pid", 0);
- mFd = intent.getIntExtra("com.android.server.logcat.fd", 0);
+ if (!intent.hasExtra(Intent.EXTRA_UID)) {
+ Slog.e(TAG, "Missing EXTRA_UID");
+ return false;
+ }
+
+ mUid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+
+ return true;
}
private Handler mHandler = new Handler() {
@@ -133,11 +140,7 @@
if (mAlert != null) {
mAlert.dismiss();
mAlert = null;
- try {
- mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
- } catch (RemoteException e) {
- Slog.e(TAG, "Fails to call remote functions", e);
- }
+ declineLogAccess();
}
break;
@@ -148,25 +151,15 @@
};
private String getTitleString(Context context, String callingPackage, int uid)
- throws Exception {
-
+ throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
- if (pm == null) {
- throw new NullPointerException("PackageManager is null");
- }
CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage,
PackageManager.MATCH_DIRECT_BOOT_AUTO,
UserHandle.getUserId(uid)).loadLabel(pm);
- if (appLabel == null || appLabel.length() == 0) {
- throw new NameNotFoundException("Application Label is null");
- }
String titleString = context.getString(
com.android.internal.R.string.log_access_confirmation_title, appLabel);
- if (titleString == null || titleString.length() == 0) {
- throw new NullPointerException("Title is null");
- }
return titleString;
}
@@ -176,9 +169,9 @@
* If we cannot retrieve the package name, it returns null and we decline the full device log
* access
*/
- private View createView() throws Exception {
-
- final View view = getLayoutInflater().inflate(
+ private View createView(@StyleRes int themeId) {
+ Context themedContext = new ContextThemeWrapper(getApplicationContext(), themeId);
+ final View view = LayoutInflater.from(themedContext).inflate(
R.layout.log_access_user_consent_dialog_permission, null /*root*/);
if (view == null) {
@@ -202,21 +195,17 @@
public void onClick(View view) {
switch (view.getId()) {
case R.id.log_access_dialog_allow_button:
- try {
- mLogcatManagerService.approve(mUid, mGid, mPid, mFd);
- } catch (RemoteException e) {
- Slog.e(TAG, "Fails to call remote functions", e);
- }
+ mLogcatManagerInternal.approveAccessForClient(mUid, mPackageName);
finish();
break;
case R.id.log_access_dialog_deny_button:
- try {
- mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
- } catch (RemoteException e) {
- Slog.e(TAG, "Fails to call remote functions", e);
- }
+ declineLogAccess();
finish();
break;
}
}
+
+ private void declineLogAccess() {
+ mLogcatManagerInternal.declineAccessForClient(mUid, mPackageName);
+ }
}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 5dccd07..21beb96 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -16,90 +16,322 @@
package com.android.server.logcat;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Handler;
import android.os.ILogd;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.logcat.ILogcatManagerService;
+import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
/**
* Service responsible for managing the access to Logcat.
*/
public final class LogcatManagerService extends SystemService {
-
private static final String TAG = "LogcatManagerService";
+ private static final boolean DEBUG = false;
+
+ /** How long to wait for the user to approve/decline before declining automatically */
+ @VisibleForTesting
+ static final int PENDING_CONFIRMATION_TIMEOUT_MILLIS = Build.IS_DEBUGGABLE ? 70000 : 400000;
+
+ /**
+ * How long an approved / declined status is valid for.
+ *
+ * After a client has been approved/declined log access, if they try to access logs again within
+ * this timeout, the new request will be automatically approved/declined.
+ * Only after this timeout expires will a new request generate another prompt to the user.
+ **/
+ @VisibleForTesting
+ static final int STATUS_EXPIRATION_TIMEOUT_MILLIS = 60 * 1000;
+
+ private static final int MSG_LOG_ACCESS_REQUESTED = 0;
+ private static final int MSG_APPROVE_LOG_ACCESS = 1;
+ private static final int MSG_DECLINE_LOG_ACCESS = 2;
+ private static final int MSG_LOG_ACCESS_FINISHED = 3;
+ private static final int MSG_PENDING_TIMEOUT = 4;
+ private static final int MSG_LOG_ACCESS_STATUS_EXPIRED = 5;
+
+ private static final int STATUS_NEW_REQUEST = 0;
+ private static final int STATUS_PENDING = 1;
+ private static final int STATUS_APPROVED = 2;
+ private static final int STATUS_DECLINED = 3;
+
+ @IntDef(prefix = {"STATUS_"}, value = {
+ STATUS_NEW_REQUEST,
+ STATUS_PENDING,
+ STATUS_APPROVED,
+ STATUS_DECLINED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LogAccessRequestStatus {
+ }
+
private final Context mContext;
+ private final Injector mInjector;
+ private final Supplier<Long> mClock;
private final BinderService mBinderService;
- private final ExecutorService mThreadExecutor;
- private ILogd mLogdService;
- private @NonNull ActivityManager mActivityManager;
+ private final LogcatManagerServiceInternal mLocalService;
+ private final Handler mHandler;
private ActivityManagerInternal mActivityManagerInternal;
- private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2;
- private static final String TARGET_PACKAGE_NAME = "android";
- private static final String TARGET_ACTIVITY_NAME =
- "com.android.server.logcat.LogAccessDialogActivity";
- private static final String EXTRA_UID = "com.android.server.logcat.uid";
- private static final String EXTRA_GID = "com.android.server.logcat.gid";
- private static final String EXTRA_PID = "com.android.server.logcat.pid";
- private static final String EXTRA_FD = "com.android.server.logcat.fd";
+ private ILogd mLogdService;
+
+ private static final class LogAccessClient {
+ final int mUid;
+ @NonNull
+ final String mPackageName;
+
+ LogAccessClient(int uid, @NonNull String packageName) {
+ mUid = uid;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof LogAccessClient)) return false;
+ LogAccessClient that = (LogAccessClient) o;
+ return mUid == that.mUid && Objects.equals(mPackageName, that.mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUid, mPackageName);
+ }
+
+ @Override
+ public String toString() {
+ return "LogAccessClient{"
+ + "mUid=" + mUid
+ + ", mPackageName=" + mPackageName
+ + '}';
+ }
+ }
+
+ private static final class LogAccessRequest {
+ final int mUid;
+ final int mGid;
+ final int mPid;
+ final int mFd;
+
+ private LogAccessRequest(int uid, int gid, int pid, int fd) {
+ mUid = uid;
+ mGid = gid;
+ mPid = pid;
+ mFd = fd;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof LogAccessRequest)) return false;
+ LogAccessRequest that = (LogAccessRequest) o;
+ return mUid == that.mUid && mGid == that.mGid && mPid == that.mPid && mFd == that.mFd;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUid, mGid, mPid, mFd);
+ }
+
+ @Override
+ public String toString() {
+ return "LogAccessRequest{"
+ + "mUid=" + mUid
+ + ", mGid=" + mGid
+ + ", mPid=" + mPid
+ + ", mFd=" + mFd
+ + '}';
+ }
+ }
+
+ private static final class LogAccessStatus {
+ @LogAccessRequestStatus
+ int mStatus = STATUS_NEW_REQUEST;
+ final List<LogAccessRequest> mPendingRequests = new ArrayList<>();
+ }
+
+ private final Map<LogAccessClient, LogAccessStatus> mLogAccessStatus = new ArrayMap<>();
+ private final Map<LogAccessClient, Integer> mActiveLogAccessCount = new ArrayMap<>();
private final class BinderService extends ILogcatManagerService.Stub {
@Override
public void startThread(int uid, int gid, int pid, int fd) {
- mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, true));
+ final LogAccessRequest logAccessRequest = new LogAccessRequest(uid, gid, pid, fd);
+ if (DEBUG) {
+ Slog.d(TAG, "New log access request: " + logAccessRequest);
+ }
+ final Message msg = mHandler.obtainMessage(MSG_LOG_ACCESS_REQUESTED, logAccessRequest);
+ mHandler.sendMessageAtTime(msg, mClock.get());
}
@Override
public void finishThread(int uid, int gid, int pid, int fd) {
- // TODO This thread will be used to notify the AppOpsManager that
- // the logd data access is finished.
- mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
+ final LogAccessRequest logAccessRequest = new LogAccessRequest(uid, gid, pid, fd);
+ if (DEBUG) {
+ Slog.d(TAG, "Log access finished: " + logAccessRequest);
+ }
+ final Message msg = mHandler.obtainMessage(MSG_LOG_ACCESS_FINISHED, logAccessRequest);
+ mHandler.sendMessageAtTime(msg, mClock.get());
+ }
+ }
+
+ final class LogcatManagerServiceInternal {
+ public void approveAccessForClient(int uid, @NonNull String packageName) {
+ final LogAccessClient client = new LogAccessClient(uid, packageName);
+ if (DEBUG) {
+ Slog.d(TAG, "Approving log access for client: " + client);
+ }
+ final Message msg = mHandler.obtainMessage(MSG_APPROVE_LOG_ACCESS, client);
+ mHandler.sendMessageAtTime(msg, mClock.get());
}
- @Override
- public void approve(int uid, int gid, int pid, int fd) {
- try {
- Slog.d(TAG, "Allow logd access for uid: " + uid);
- getLogdService().approve(uid, gid, pid, fd);
- } catch (RemoteException e) {
- Slog.e(TAG, "Fails to call remote functions", e);
+ public void declineAccessForClient(int uid, @NonNull String packageName) {
+ final LogAccessClient client = new LogAccessClient(uid, packageName);
+ if (DEBUG) {
+ Slog.d(TAG, "Declining log access for client: " + client);
}
- }
-
- @Override
- public void decline(int uid, int gid, int pid, int fd) {
- try {
- Slog.d(TAG, "Decline logd access for uid: " + uid);
- getLogdService().decline(uid, gid, pid, fd);
- } catch (RemoteException e) {
- Slog.e(TAG, "Fails to call remote functions", e);
- }
+ final Message msg = mHandler.obtainMessage(MSG_DECLINE_LOG_ACCESS, client);
+ mHandler.sendMessageAtTime(msg, mClock.get());
}
}
private ILogd getLogdService() {
- synchronized (LogcatManagerService.this) {
- if (mLogdService == null) {
- LogcatManagerService.this.addLogdService();
- }
- return mLogdService;
+ if (mLogdService == null) {
+ mLogdService = mInjector.getLogdService();
}
+ return mLogdService;
+ }
+
+ private static class LogAccessRequestHandler extends Handler {
+ private final LogcatManagerService mService;
+
+ LogAccessRequestHandler(Looper looper, LogcatManagerService service) {
+ super(looper);
+ mService = service;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOG_ACCESS_REQUESTED: {
+ LogAccessRequest request = (LogAccessRequest) msg.obj;
+ mService.onLogAccessRequested(request);
+ break;
+ }
+ case MSG_APPROVE_LOG_ACCESS: {
+ LogAccessClient client = (LogAccessClient) msg.obj;
+ mService.onAccessApprovedForClient(client);
+ break;
+ }
+ case MSG_DECLINE_LOG_ACCESS: {
+ LogAccessClient client = (LogAccessClient) msg.obj;
+ mService.onAccessDeclinedForClient(client);
+ break;
+ }
+ case MSG_LOG_ACCESS_FINISHED: {
+ LogAccessRequest request = (LogAccessRequest) msg.obj;
+ mService.onLogAccessFinished(request);
+ break;
+ }
+ case MSG_PENDING_TIMEOUT: {
+ LogAccessClient client = (LogAccessClient) msg.obj;
+ mService.onPendingTimeoutExpired(client);
+ break;
+ }
+ case MSG_LOG_ACCESS_STATUS_EXPIRED: {
+ LogAccessClient client = (LogAccessClient) msg.obj;
+ mService.onAccessStatusExpired(client);
+ break;
+ }
+ }
+ }
+ }
+
+ static class Injector {
+ protected Supplier<Long> createClock() {
+ return SystemClock::uptimeMillis;
+ }
+
+ protected Looper getLooper() {
+ return Looper.getMainLooper();
+ }
+
+ protected ILogd getLogdService() {
+ return ILogd.Stub.asInterface(ServiceManager.getService("logd"));
+ }
+ }
+
+ public LogcatManagerService(Context context) {
+ this(context, new Injector());
+ }
+
+ public LogcatManagerService(Context context, Injector injector) {
+ super(context);
+ mContext = context;
+ mInjector = injector;
+ mClock = injector.createClock();
+ mBinderService = new BinderService();
+ mLocalService = new LogcatManagerServiceInternal();
+ mHandler = new LogAccessRequestHandler(injector.getLooper(), this);
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ publishBinderService("logcat", mBinderService);
+ publishLocalService(LogcatManagerServiceInternal.class, mLocalService);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+ }
+ }
+
+ @VisibleForTesting
+ LogcatManagerServiceInternal getLocalService() {
+ return mLocalService;
+ }
+
+ @VisibleForTesting
+ ILogcatManagerService getBinderService() {
+ return mBinderService;
+ }
+
+ @Nullable
+ private LogAccessClient getClientForRequest(LogAccessRequest request) {
+ final String packageName = getPackageName(request);
+ if (packageName == null) {
+ return null;
+ }
+
+ return new LogAccessClient(request.mUid, packageName);
}
/**
@@ -107,12 +339,9 @@
* If we cannot retrieve the package name, it returns null and we decline the full device log
* access
*/
- private String getPackageName(int uid, int gid, int pid, int fd) {
-
- final ActivityManagerInternal activityManagerInternal =
- LocalServices.getService(ActivityManagerInternal.class);
- if (activityManagerInternal != null) {
- String packageName = activityManagerInternal.getPackageNameByPid(pid);
+ private String getPackageName(LogAccessRequest request) {
+ if (mActivityManagerInternal != null) {
+ String packageName = mActivityManagerInternal.getPackageNameByPid(request.mPid);
if (packageName != null) {
return packageName;
}
@@ -125,7 +354,7 @@
return null;
}
- String[] packageNames = pm.getPackagesForUid(uid);
+ String[] packageNames = pm.getPackagesForUid(request.mUid);
if (ArrayUtils.isEmpty(packageNames)) {
// Decline the logd access if the app name is unknown
@@ -142,127 +371,164 @@
}
return firstPackageName;
-
}
- private void declineLogdAccess(int uid, int gid, int pid, int fd) {
+ void onLogAccessRequested(LogAccessRequest request) {
+ final LogAccessClient client = getClientForRequest(request);
+ if (client == null) {
+ declineRequest(request);
+ return;
+ }
+
+ LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
+ if (logAccessStatus == null) {
+ logAccessStatus = new LogAccessStatus();
+ mLogAccessStatus.put(client, logAccessStatus);
+ }
+
+ switch (logAccessStatus.mStatus) {
+ case STATUS_NEW_REQUEST:
+ logAccessStatus.mPendingRequests.add(request);
+ processNewLogAccessRequest(client);
+ break;
+ case STATUS_PENDING:
+ logAccessStatus.mPendingRequests.add(request);
+ return;
+ case STATUS_APPROVED:
+ approveRequest(client, request);
+ break;
+ case STATUS_DECLINED:
+ declineRequest(request);
+ break;
+ }
+ }
+
+ private boolean shouldShowConfirmationDialog(LogAccessClient client) {
+ // If the process is foreground, show a dialog for user consent
+ final int procState = mActivityManagerInternal.getUidProcessState(client.mUid);
+ return procState == ActivityManager.PROCESS_STATE_TOP;
+ }
+
+ private void processNewLogAccessRequest(LogAccessClient client) {
+ boolean isInstrumented = mActivityManagerInternal.isUidCurrentlyInstrumented(client.mUid);
+
+ // The instrumented apks only run for testing, so we don't check user permission.
+ if (isInstrumented) {
+ onAccessApprovedForClient(client);
+ return;
+ }
+
+ if (!shouldShowConfirmationDialog(client)) {
+ onAccessDeclinedForClient(client);
+ return;
+ }
+
+ final LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
+ logAccessStatus.mStatus = STATUS_PENDING;
+
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_PENDING_TIMEOUT, client),
+ mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS);
+ final Intent mIntent = createIntent(client);
+ mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
+ }
+
+ void onAccessApprovedForClient(LogAccessClient client) {
+ scheduleStatusExpiry(client);
+
+ LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
+ if (logAccessStatus != null) {
+ for (LogAccessRequest request : logAccessStatus.mPendingRequests) {
+ approveRequest(client, request);
+ }
+ logAccessStatus.mStatus = STATUS_APPROVED;
+ logAccessStatus.mPendingRequests.clear();
+ }
+ }
+
+ void onAccessDeclinedForClient(LogAccessClient client) {
+ scheduleStatusExpiry(client);
+
+ LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
+ if (logAccessStatus != null) {
+ for (LogAccessRequest request : logAccessStatus.mPendingRequests) {
+ declineRequest(request);
+ }
+ logAccessStatus.mStatus = STATUS_DECLINED;
+ logAccessStatus.mPendingRequests.clear();
+ }
+ }
+
+ private void scheduleStatusExpiry(LogAccessClient client) {
+ mHandler.removeMessages(MSG_PENDING_TIMEOUT, client);
+ mHandler.removeMessages(MSG_LOG_ACCESS_STATUS_EXPIRED, client);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_LOG_ACCESS_STATUS_EXPIRED, client),
+ mClock.get() + STATUS_EXPIRATION_TIMEOUT_MILLIS);
+ }
+
+ void onPendingTimeoutExpired(LogAccessClient client) {
+ final LogAccessStatus logAccessStatus = mLogAccessStatus.get(client);
+ if (logAccessStatus != null && logAccessStatus.mStatus == STATUS_PENDING) {
+ onAccessDeclinedForClient(client);
+ }
+ }
+
+ void onAccessStatusExpired(LogAccessClient client) {
+ if (DEBUG) {
+ Slog.d(TAG, "Log access status expired for " + client);
+ }
+ mLogAccessStatus.remove(client);
+ }
+
+ void onLogAccessFinished(LogAccessRequest request) {
+ final LogAccessClient client = getClientForRequest(request);
+ final int activeCount = mActiveLogAccessCount.getOrDefault(client, 1) - 1;
+
+ if (activeCount == 0) {
+ mActiveLogAccessCount.remove(client);
+ if (DEBUG) {
+ Slog.d(TAG, "Client is no longer accessing logs: " + client);
+ }
+ // TODO This will be used to notify the AppOpsManager that the logd data access
+ // is finished.
+ } else {
+ mActiveLogAccessCount.put(client, activeCount);
+ }
+ }
+
+ private void approveRequest(LogAccessClient client, LogAccessRequest request) {
+ if (DEBUG) {
+ Slog.d(TAG, "Approving log access: " + request);
+ }
try {
- getLogdService().decline(uid, gid, pid, fd);
+ getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+ Integer activeCount = mActiveLogAccessCount.getOrDefault(client, 0);
+ mActiveLogAccessCount.put(client, activeCount + 1);
} catch (RemoteException e) {
Slog.e(TAG, "Fails to call remote functions", e);
}
}
- private static String getClientInfo(int uid, int gid, int pid, int fd) {
- return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID="
- + Integer.toString(pid) + " FD=" + Integer.toString(fd);
- }
-
- private class LogdMonitor implements Runnable {
-
- private final int mUid;
- private final int mGid;
- private final int mPid;
- private final int mFd;
- private final boolean mStart;
-
- /**
- * For starting a thread, the start value is true.
- * For finishing a thread, the start value is false.
- */
- LogdMonitor(int uid, int gid, int pid, int fd, boolean start) {
- mUid = uid;
- mGid = gid;
- mPid = pid;
- mFd = fd;
- mStart = start;
+ private void declineRequest(LogAccessRequest request) {
+ if (DEBUG) {
+ Slog.d(TAG, "Declining log access: " + request);
}
-
- /**
- * LogdMonitor generates a prompt for users.
- * The users decide whether the logd access is allowed.
- */
- @Override
- public void run() {
- if (mLogdService == null) {
- LogcatManagerService.this.addLogdService();
- }
-
- if (mStart) {
-
- ActivityManagerInternal ami = LocalServices.getService(
- ActivityManagerInternal.class);
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
-
- // The instrumented apks only run for testing, so we don't check user permission.
- if (isCallerInstrumented) {
- try {
- getLogdService().approve(mUid, mGid, mPid, mFd);
- } catch (RemoteException e) {
- Slog.e(TAG, "Fails to call remote functions", e);
- }
- return;
- }
-
- final int procState = LocalServices.getService(ActivityManagerInternal.class)
- .getUidProcessState(mUid);
- // If the process is foreground and we can retrieve the package name, show a dialog
- // for user consent
- if (procState == ActivityManager.PROCESS_STATE_TOP) {
- String packageName = getPackageName(mUid, mGid, mPid, mFd);
- if (packageName != null) {
- final Intent mIntent = createIntent(packageName, mUid, mGid, mPid, mFd);
- mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
- return;
- }
- }
-
- /**
- * If the process is background or cannot retrieve the package name,
- * decline the logd access.
- **/
- declineLogdAccess(mUid, mGid, mPid, mFd);
- return;
- }
- }
- }
-
- public LogcatManagerService(Context context) {
- super(context);
- mContext = context;
- mBinderService = new BinderService();
- mThreadExecutor = Executors.newCachedThreadPool();
- mActivityManager = context.getSystemService(ActivityManager.class);
- }
-
- @Override
- public void onStart() {
try {
- publishBinderService("logcat", mBinderService);
- } catch (Throwable t) {
- Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+ getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Fails to call remote functions", e);
}
}
- private void addLogdService() {
- mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
- }
-
/**
* Create the Intent for LogAccessDialogActivity.
*/
- public Intent createIntent(String targetPackageName, int uid, int gid, int pid, int fd) {
- final Intent intent = new Intent();
+ public Intent createIntent(LogAccessClient client) {
+ final Intent intent = new Intent(mContext, LogAccessDialogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
- intent.putExtra(EXTRA_UID, uid);
- intent.putExtra(EXTRA_GID, gid);
- intent.putExtra(EXTRA_PID, pid);
- intent.putExtra(EXTRA_FD, fd);
-
- intent.setComponent(new ComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME));
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName);
+ intent.putExtra(Intent.EXTRA_UID, client.mUid);
return intent;
}
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 02f9ceb..89902f7 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -51,14 +51,16 @@
void onNotificationSettingsViewed(String key);
/**
* Called when the state of {@link Notification#FLAG_BUBBLE} is changed.
+ *
+ * @param key the notification key
+ * @param isBubble whether the notification should have {@link Notification#FLAG_BUBBLE} applied
+ * @param flags the flags to apply to the notification's {@link Notification.BubbleMetadata}
*/
void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
/**
- * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION}
- * or {@link Notification.BubbleMetadata#FLAG_SUPPRESS_BUBBLE} changes.
+ * Called when the flags on {@link Notification.BubbleMetadata} are changed.
*/
- void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
- boolean isBubbleSuppressed);
+ void onBubbleMetadataFlagChanged(String key, int flags);
/**
* Grant permission to read the specified URI to the package associated with the
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index c548e7e..8a62736 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -42,4 +42,7 @@
/** Does the specified package/uid have permission to post notifications? */
boolean areNotificationsEnabledForPackage(String pkg, int uid);
+
+ /** Send a notification to the user prompting them to review their notification permissions. */
+ void sendReviewPermissionsNotification();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6078bfc..83c576e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -274,6 +275,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
@@ -442,6 +444,18 @@
private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13);
+ // States for the review permissions notification
+ static final int REVIEW_NOTIF_STATE_UNKNOWN = -1;
+ static final int REVIEW_NOTIF_STATE_SHOULD_SHOW = 0;
+ static final int REVIEW_NOTIF_STATE_USER_INTERACTED = 1;
+ static final int REVIEW_NOTIF_STATE_DISMISSED = 2;
+ static final int REVIEW_NOTIF_STATE_RESHOWN = 3;
+
+ // Action strings for review permissions notification
+ static final String REVIEW_NOTIF_ACTION_REMIND = "REVIEW_NOTIF_ACTION_REMIND";
+ static final String REVIEW_NOTIF_ACTION_DISMISS = "REVIEW_NOTIF_ACTION_DISMISS";
+ static final String REVIEW_NOTIF_ACTION_CANCELED = "REVIEW_NOTIF_ACTION_CANCELED";
+
/**
* Apps that post custom toasts in the background will have those blocked. Apps can
* still post toasts created with
@@ -652,6 +666,9 @@
private InstanceIdSequence mNotificationInstanceIdSequence;
private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
+ // Broadcast intent receiver for notification permissions review-related intents
+ private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
+
static class Archive {
final SparseArray<Boolean> mEnabled;
final int mBufferSize;
@@ -1413,8 +1430,7 @@
}
@Override
- public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
- boolean isBubbleSuppressed) {
+ public void onBubbleMetadataFlagChanged(String key, int flags) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
@@ -1424,17 +1440,12 @@
return;
}
- boolean flagChanged = false;
- if (data.isNotificationSuppressed() != isNotifSuppressed) {
- flagChanged = true;
- data.setSuppressNotification(isNotifSuppressed);
- }
- if (data.isBubbleSuppressed() != isBubbleSuppressed) {
- flagChanged = true;
- data.setSuppressBubble(isBubbleSuppressed);
- }
- if (flagChanged) {
+ if (flags != data.getFlags()) {
+ data.setFlags(flags);
+ // Shouldn't alert again just because of a flag change.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+ // Force isAppForeground true here, because for sysui's purposes we
+ // want to be able to adjust the flag behaviour.
mHandler.post(
new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
true /* isAppForeground */, SystemClock.elapsedRealtime()));
@@ -2416,6 +2427,11 @@
IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
+
+ mReviewNotificationPermissionsReceiver = new ReviewNotificationPermissionsReceiver();
+ getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
+ ReviewNotificationPermissionsReceiver.getFilter(),
+ Context.RECEIVER_NOT_EXPORTED);
}
/**
@@ -2709,6 +2725,7 @@
mHistoryManager.onBootPhaseAppsCanStart();
registerDeviceConfigChange();
migrateDefaultNAS();
+ maybeShowInitialReviewPermissionsNotification();
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
}
@@ -6336,6 +6353,21 @@
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
return areNotificationsEnabledForPackageInt(pkg, uid);
}
+
+ @Override
+ public void sendReviewPermissionsNotification() {
+ // This method is meant to be called from the JobService upon running the job for this
+ // notification having been rescheduled; so without checking any other state, it will
+ // send the notification.
+ checkCallerIsSystem();
+ NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+ nm.notify(TAG,
+ SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
+ createReviewPermissionsNotification());
+ Settings.Global.putInt(getContext().getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+ }
};
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -7145,10 +7177,12 @@
&& r.getNotification().isBubbleNotification())
|| (mReason == REASON_CLICK && r.canBubble()
&& r.isFlagBubbleRemoved())) {
- boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null
- && r.getNotification().getBubbleMetadata().isBubbleSuppressed();
- mNotificationDelegate.onBubbleNotificationSuppressionChanged(
- r.getKey(), true /* notifSuppressed */, isBubbleSuppressed);
+ int flags = 0;
+ if (r.getNotification().getBubbleMetadata() != null) {
+ flags = r.getNotification().getBubbleMetadata().getFlags();
+ }
+ flags |= FLAG_SUPPRESS_NOTIFICATION;
+ mNotificationDelegate.onBubbleMetadataFlagChanged(r.getKey(), flags);
return;
}
if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) {
@@ -11608,6 +11642,76 @@
out.endTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
}
+ // Creates a notification that informs the user about changes due to the migration to
+ // use permissions for notifications.
+ protected Notification createReviewPermissionsNotification() {
+ int title = R.string.review_notification_settings_title;
+ int content = R.string.review_notification_settings_text;
+
+ // Tapping on the notification leads to the settings screen for managing app notifications,
+ // using the intent reserved for system services to indicate it comes from this notification
+ Intent tapIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW);
+ Intent remindIntent = new Intent(REVIEW_NOTIF_ACTION_REMIND);
+ Intent dismissIntent = new Intent(REVIEW_NOTIF_ACTION_DISMISS);
+ Intent swipeIntent = new Intent(REVIEW_NOTIF_ACTION_CANCELED);
+
+ // Both "remind me" and "dismiss" actions will be actions received by the BroadcastReceiver
+ final Notification.Action remindMe = new Notification.Action.Builder(null,
+ getContext().getResources().getString(
+ R.string.review_notification_settings_remind_me_action),
+ PendingIntent.getBroadcast(
+ getContext(), 0, remindIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .build();
+ final Notification.Action dismiss = new Notification.Action.Builder(null,
+ getContext().getResources().getString(
+ R.string.review_notification_settings_dismiss),
+ PendingIntent.getBroadcast(
+ getContext(), 0, dismissIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .build();
+
+ return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES)
+ .setSmallIcon(R.drawable.stat_sys_adb)
+ .setContentTitle(getContext().getResources().getString(title))
+ .setContentText(getContext().getResources().getString(content))
+ .setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .setStyle(new Notification.BigTextStyle())
+ .setFlag(Notification.FLAG_NO_CLEAR, true)
+ .setAutoCancel(true)
+ .addAction(remindMe)
+ .addAction(dismiss)
+ .setDeleteIntent(PendingIntent.getBroadcast(getContext(), 0, swipeIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .build();
+ }
+
+ protected void maybeShowInitialReviewPermissionsNotification() {
+ int currentState = Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ REVIEW_NOTIF_STATE_UNKNOWN);
+
+ // now check the last known state of the notification -- this determination of whether the
+ // user is in the correct target audience occurs elsewhere, and will have written the
+ // REVIEW_NOTIF_STATE_SHOULD_SHOW to indicate it should be shown in the future.
+ //
+ // alternatively, if the user has rescheduled the notification (so it has been shown
+ // again) but not yet interacted with the new notification, then show it again on boot,
+ // as this state indicates that the user had the notification open before rebooting.
+ //
+ // sending the notification here does not record a new state for the notification;
+ // that will be written by parts of the system further down the line if at any point
+ // the user interacts with the notification.
+ if (currentState == REVIEW_NOTIF_STATE_SHOULD_SHOW
+ || currentState == REVIEW_NOTIF_STATE_RESHOWN) {
+ NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+ nm.notify(TAG,
+ SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
+ createReviewPermissionsNotification());
+ }
+ }
+
/**
* Shows a warning on logcat. Shows the toast only once per package. This is to avoid being too
* aggressive and annoying the user.
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index a09aa7c..b4230c1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -55,10 +55,8 @@
private final PermissionManagerServiceInternal mPmi;
private final IPackageManager mPackageManager;
private final IPermissionManager mPermManager;
- // TODO (b/194833441): Remove this boolean (but keep the isMigrationEnabled() method)
- // when the migration is enabled
+ // TODO (b/194833441): Remove when the migration is enabled
private final boolean mMigrationEnabled;
- private final boolean mIsTv;
private final boolean mForceUserSetOnUpgrade;
public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
@@ -69,17 +67,10 @@
mPermManager = permManager;
mMigrationEnabled = migrationEnabled;
mForceUserSetOnUpgrade = forceUserSetOnUpgrade;
- boolean isTv;
- try {
- isTv = mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0);
- } catch (RemoteException e) {
- isTv = false;
- }
- mIsTv = isTv;
}
public boolean isMigrationEnabled() {
- return mMigrationEnabled && !mIsTv;
+ return mMigrationEnabled;
}
/**
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0525b1e..ef3c770 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -96,6 +96,10 @@
private final int XML_VERSION;
/** What version to check to do the upgrade for bubbles. */
private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
+ /** The first xml version with notification permissions enabled. */
+ private static final int XML_VERSION_NOTIF_PERMISSION = 3;
+ /** The first xml version that notifies users to review their notification permissions */
+ private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
@VisibleForTesting
static final int UNKNOWN_UID = UserHandle.USER_NULL;
private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
@@ -206,7 +210,7 @@
mStatsEventBuilderFactory = statsEventBuilderFactory;
if (mPermissionHelper.isMigrationEnabled()) {
- XML_VERSION = 3;
+ XML_VERSION = 4;
} else {
XML_VERSION = 2;
}
@@ -226,8 +230,16 @@
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
- boolean migrateToPermission =
- (xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled();
+ boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION)
+ && mPermissionHelper.isMigrationEnabled();
+ if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) {
+ // make a note that we should show the notification at some point.
+ // it shouldn't be possible for the user to already have seen it, as the XML version
+ // would be newer then.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+ }
ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
synchronized (mPackagePreferences) {
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
new file mode 100644
index 0000000..fde45f71
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+/**
+ * JobService implementation for scheduling the notification informing users about
+ * notification permissions updates and taking them to review their existing permissions.
+ * @hide
+ */
+public class ReviewNotificationPermissionsJobService extends JobService {
+ public static final String TAG = "ReviewNotificationPermissionsJobService";
+
+ @VisibleForTesting
+ protected static final int JOB_ID = 225373531;
+
+ /**
+ * Schedule a new job that will show a notification the specified amount of time in the future.
+ */
+ public static void scheduleJob(Context context, long rescheduleTimeMillis) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ // if the job already exists for some reason, cancel & reschedule
+ if (jobScheduler.getPendingJob(JOB_ID) != null) {
+ jobScheduler.cancel(JOB_ID);
+ }
+ ComponentName component = new ComponentName(
+ context, ReviewNotificationPermissionsJobService.class);
+ JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
+ .setPersisted(true) // make sure it'll still get rescheduled after reboot
+ .setMinimumLatency(rescheduleTimeMillis) // run after specified amount of time
+ .build();
+ jobScheduler.schedule(newJob);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ // While jobs typically should be run on different threads, this
+ // job only posts a notification, which is not a long-running operation
+ // as notification posting is asynchronous.
+ NotificationManagerInternal nmi =
+ LocalServices.getService(NotificationManagerInternal.class);
+ nmi.sendReviewPermissionsNotification();
+
+ // once the notification is posted, the job is done, so no need to
+ // keep it alive afterwards
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // If we're interrupted for some reason, try again (though this may not
+ // ever happen due to onStartJob not leaving a job running after being
+ // called)
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java
new file mode 100644
index 0000000..b99aeac
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Broadcast receiver for intents that come from the "review notification permissions" notification,
+ * shown to users who upgrade to T from an earlier OS to inform them of notification setup changes
+ * and invite them to review their notification permissions.
+ */
+public class ReviewNotificationPermissionsReceiver extends BroadcastReceiver {
+ public static final String TAG = "ReviewNotifPermissions";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // 7 days in millis, as the amount of time to wait before re-sending the notification
+ private static final long JOB_RESCHEDULE_TIME = 1000 /* millis */ * 60 /* seconds */
+ * 60 /* minutes */ * 24 /* hours */ * 7 /* days */;
+
+ static IntentFilter getFilter() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+ filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
+ filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+ return filter;
+ }
+
+ // Cancels the "review notification permissions" notification.
+ @VisibleForTesting
+ protected void cancelNotification(Context context) {
+ NotificationManager nm = context.getSystemService(NotificationManager.class);
+ if (nm != null) {
+ nm.cancel(NotificationManagerService.TAG,
+ SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS);
+ } else {
+ Slog.w(TAG, "could not cancel notification: NotificationManager not found");
+ }
+ }
+
+ @VisibleForTesting
+ protected void rescheduleNotification(Context context) {
+ ReviewNotificationPermissionsJobService.scheduleJob(context, JOB_RESCHEDULE_TIME);
+ // log if needed
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduled review permissions notification for on or after: "
+ + LocalDateTime.now(ZoneId.systemDefault())
+ .plus(JOB_RESCHEDULE_TIME, ChronoUnit.MILLIS));
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND)) {
+ // Reschedule the notification for 7 days in the future
+ rescheduleNotification(context);
+
+ // note that the user has interacted; no longer needed to show the initial
+ // notification
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ cancelNotification(context);
+ } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS)) {
+ // user dismissed; write to settings so we don't show ever again
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+ cancelNotification(context);
+ } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED)) {
+ // we may get here from the user swiping away the notification,
+ // or from the notification being canceled in any other way.
+ // only in the case that the user hasn't interacted with it in
+ // any other way yet, reschedule
+ int notifState = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ /* default */ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
+ if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW) {
+ // user hasn't interacted in the past, so reschedule once and then note that the
+ // user *has* interacted now so we don't re-reschedule if they swipe again
+ rescheduleNotification(context);
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ } else if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN) {
+ // swiping away on a rescheduled notification; mark as interacted and
+ // don't reschedule again.
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/AppIdSettingMap.java b/services/core/java/com/android/server/pm/AppIdSettingMap.java
index b41a0b8..20c67f8 100644
--- a/services/core/java/com/android/server/pm/AppIdSettingMap.java
+++ b/services/core/java/com/android/server/pm/AppIdSettingMap.java
@@ -19,6 +19,7 @@
import android.os.Process;
import android.util.Log;
+import com.android.server.utils.SnapshotCache;
import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedSparseArray;
import com.android.server.utils.Watcher;
@@ -34,10 +35,28 @@
* index to the corresponding SettingBase object is (appId - FIRST_APPLICATION_ID). If an app ID
* doesn't exist (i.e., app is not installed), we fill the corresponding entry with null.
*/
- private WatchedArrayList<SettingBase> mNonSystemSettings = new WatchedArrayList<>();
- private WatchedSparseArray<SettingBase> mSystemSettings = new WatchedSparseArray<>();
+ private final WatchedArrayList<SettingBase> mNonSystemSettings;
+ private final SnapshotCache<WatchedArrayList<SettingBase>> mNonSystemSettingsSnapshot;
+ private final WatchedSparseArray<SettingBase> mSystemSettings;
+ private final SnapshotCache<WatchedSparseArray<SettingBase>> mSystemSettingsSnapshot;
private int mFirstAvailableAppId = Process.FIRST_APPLICATION_UID;
+ AppIdSettingMap() {
+ mNonSystemSettings = new WatchedArrayList<>();
+ mNonSystemSettingsSnapshot = new SnapshotCache.Auto<>(
+ mNonSystemSettings, mNonSystemSettings, "AppIdSettingMap.mNonSystemSettings");
+ mSystemSettings = new WatchedSparseArray<>();
+ mSystemSettingsSnapshot = new SnapshotCache.Auto<>(
+ mSystemSettings, mSystemSettings, "AppIdSettingMap.mSystemSettings");
+ }
+
+ AppIdSettingMap(AppIdSettingMap orig) {
+ mNonSystemSettings = orig.mNonSystemSettingsSnapshot.snapshot();
+ mNonSystemSettingsSnapshot = new SnapshotCache.Sealed<>();
+ mSystemSettings = orig.mSystemSettingsSnapshot.snapshot();
+ mSystemSettingsSnapshot = new SnapshotCache.Sealed<>();
+ }
+
/** Returns true if the requested AppID was valid and not already registered. */
public boolean registerExistingAppId(int appId, SettingBase setting, Object name) {
if (appId >= Process.FIRST_APPLICATION_UID) {
@@ -134,10 +153,7 @@
}
public AppIdSettingMap snapshot() {
- AppIdSettingMap l = new AppIdSettingMap();
- mNonSystemSettings.snapshot(l.mNonSystemSettings, mNonSystemSettings);
- mSystemSettings.snapshot(l.mSystemSettings, mSystemSettings);
- return l;
+ return new AppIdSettingMap(this);
}
public void registerObserver(Watcher observer) {
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 89f8be2..daac7c0 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -236,13 +236,6 @@
verifyActivityCanHandleIntent(launchIntent, callingUid, userId);
- // Always show the cross profile animation
- if (options == null) {
- options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
- } else {
- options.putAll(ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle());
- }
-
mInjector.getActivityTaskManagerInternal()
.startActivityAsUser(
caller,
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 8c33dd9..f1296e0 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -29,7 +29,9 @@
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
+import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.Intent;
@@ -42,6 +44,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -250,10 +253,60 @@
numberOfPackagesFailed};
}
+ /**
+ * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
+ * compiles it if needed.
+ */
+ private void checkAndDexOptSystemUi() {
+ Computer snapshot = mPm.snapshotComputer();
+ String sysUiPackageName =
+ mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
+ AndroidPackage pkg = snapshot.getPackage(sysUiPackageName);
+ if (pkg == null) {
+ Log.w(TAG, "System UI package " + sysUiPackageName + " is not found for dexopting");
+ return;
+ }
+
+ boolean useProfileForDexopt = false;
+ File profileFile = new File(getPrebuildProfilePath(pkg));
+ // Copy the profile to the reference profile path if it exists. Installd can only use a
+ // profile at the reference profile path for dexopt.
+ if (profileFile.exists()) {
+ try {
+ synchronized (mPm.mInstallLock) {
+ if (mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+ pkg.getUid(), pkg.getPackageName(),
+ ArtManager.getProfileName(null))) {
+ useProfileForDexopt = true;
+ } else {
+ Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath());
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e);
+ }
+ }
+
+ // It could also be after mainline update, but we're not introducing a new reason just for
+ // this special case.
+ performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA,
+ useProfileForDexopt ? "speed-profile" : "speed", null /* splitName */,
+ 0 /* dexoptFlags */));
+ }
+
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public void performPackageDexOptUpgradeIfNeeded() {
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can request package update");
+ // The default is "true".
+ if (!"false".equals(DeviceConfig.getProperty("runtime", "dexopt_system_ui_on_boot"))) {
+ // System UI is important to user experience, so we check it on every boot. It may need
+ // to be re-compiled after a mainline update or an OTA.
+ // TODO(b/227310505): Only do this after a mainline update or an OTA.
+ checkAndDexOptSystemUi();
+ }
+
// We need to re-extract after an OTA.
boolean causeUpgrade = mPm.isDeviceUpgrading();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index fef6ce1..4df54b7 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -349,7 +349,7 @@
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
- mShortcutUser.mService.removeIconLocked(shortcut);
+ removeIcon(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
| ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
}
@@ -366,7 +366,7 @@
forceDeleteShortcutInner(newShortcut.getId());
// Extract Icon and update the icon res ID and the bitmap path.
- s.saveIconAndFixUpShortcutLocked(newShortcut);
+ s.saveIconAndFixUpShortcutLocked(this, newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
saveShortcut(newShortcut);
}
@@ -972,7 +972,8 @@
/**
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
- public ArraySet<String> getUsedBitmapFiles() {
+ @GuardedBy("mLock")
+ private ArraySet<String> getUsedBitmapFilesLocked() {
final ArraySet<String> usedFiles = new ArraySet<>(1);
forEachShortcut(si -> {
if (si.getBitmapPath() != null) {
@@ -982,6 +983,26 @@
return usedFiles;
}
+ public void cleanupDanglingBitmapFiles(@NonNull File path) {
+ synchronized (mLock) {
+ mShortcutBitmapSaver.waitForAllSavesLocked();
+ final ArraySet<String> usedFiles = getUsedBitmapFilesLocked();
+
+ for (File child : path.listFiles()) {
+ if (!child.isFile()) {
+ continue;
+ }
+ final String name = child.getName();
+ if (!usedFiles.contains(name)) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
+ }
+ child.delete();
+ }
+ }
+ }
+ }
+
private static String getFileName(@NonNull String path) {
final int sep = path.lastIndexOf(File.separatorChar);
if (sep == -1) {
@@ -1608,6 +1629,11 @@
pw.print(" (");
pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
pw.println(")");
+
+ pw.println();
+ synchronized (mLock) {
+ mShortcutBitmapSaver.dumpLocked(pw, " ");
+ }
}
public void dumpShortcuts(@NonNull PrintWriter pw, int matchFlags) {
@@ -1729,7 +1755,7 @@
// Note: at this point no shortcuts should have bitmaps pending save, but if they do,
// just remove the bitmap.
if (si.isIconPendingSave()) {
- s.removeIconLocked(si);
+ removeIcon(si);
}
out.startTag(null, TAG_SHORTCUT);
ShortcutService.writeAttr(out, ATTR_ID, si.getId());
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 6e0436f..7800183 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -16,8 +16,10 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.TypedXmlSerializer;
@@ -50,6 +52,9 @@
protected ShortcutUser mShortcutUser;
+ @GuardedBy("mLock")
+ protected ShortcutBitmapSaver mShortcutBitmapSaver;
+
protected final Object mLock = new Object();
protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
@@ -59,6 +64,7 @@
mPackageUserId = packageUserId;
mPackageName = Preconditions.checkStringNotEmpty(packageName);
mPackageInfo = Objects.requireNonNull(packageInfo);
+ mShortcutBitmapSaver = new ShortcutBitmapSaver(shortcutUser.mService);
}
/**
@@ -206,7 +212,7 @@
void saveShortcutPackageItem() {
// Wait for bitmap saves to conclude before proceeding to saving shortcuts.
- mShortcutUser.mService.waitForBitmapSaves();
+ waitForBitmapSaves();
// Save each ShortcutPackageItem in a separate Xml file.
final File path = getShortcutPackageItemFile();
if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
@@ -221,6 +227,35 @@
}
}
+ public boolean waitForBitmapSaves() {
+ synchronized (mLock) {
+ return mShortcutBitmapSaver.waitForAllSavesLocked();
+ }
+ }
+
+ public void saveBitmap(ShortcutInfo shortcut,
+ int maxDimension, Bitmap.CompressFormat format, int quality) {
+ synchronized (mLock) {
+ mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality);
+ }
+ }
+
+ /**
+ * Wait for all pending saves to finish, and then return the given shortcut's bitmap path.
+ */
+ @Nullable
+ public String getBitmapPathMayWait(ShortcutInfo shortcut) {
+ synchronized (mLock) {
+ return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut);
+ }
+ }
+
+ public void removeIcon(ShortcutInfo shortcut) {
+ synchronized (mLock) {
+ mShortcutBitmapSaver.removeIcon(shortcut);
+ }
+ }
+
void removeShortcutPackageItem() {
synchronized (mLock) {
getShortcutPackageItemFile().delete();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 780f976..f2bcf5e 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -363,7 +363,6 @@
private final RoleManager mRoleManager;
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
- private final ShortcutBitmapSaver mShortcutBitmapSaver;
private final ShortcutDumpFiles mShortcutDumpFiles;
@GuardedBy("mLock")
@@ -490,7 +489,6 @@
mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class));
mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
- mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, true)
@@ -1063,8 +1061,6 @@
Slog.d(TAG, "Saving to " + path);
}
- mShortcutBitmapSaver.waitForAllSavesLocked();
-
path.getParentFile().mkdirs();
final AtomicFile file = new AtomicFile(path);
FileOutputStream os = null;
@@ -1388,15 +1384,12 @@
// === Caller validation ===
- void removeIconLocked(ShortcutInfo shortcut) {
- mShortcutBitmapSaver.removeIcon(shortcut);
- }
-
public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
if (!packagePath.isDirectory()) {
return;
}
+ // ShortcutPackage is already removed at this point, we can safely remove the folder.
if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
Slog.w(TAG, "Unable to remove directory " + packagePath);
}
@@ -1437,38 +1430,12 @@
}
cleanupBitmapsForPackage(userId, packageName);
} else {
- cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
+ user.getPackageShortcuts(packageName).cleanupDanglingBitmapFiles(child);
}
}
logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
}
- /**
- * Remove dangling bitmap files for a package.
- *
- * Note this method must be called with the lock held after calling
- * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
- * saves are going on.
- */
- private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
- @NonNull String packageName, @NonNull File path) {
- final ArraySet<String> usedFiles =
- user.getPackageShortcuts(packageName).getUsedBitmapFiles();
-
- for (File child : path.listFiles()) {
- if (!child.isFile()) {
- continue;
- }
- final String name = child.getName();
- if (!usedFiles.contains(name)) {
- if (DEBUG) {
- Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
- }
- child.delete();
- }
- }
- }
-
@VisibleForTesting
static class FileOutputStreamWithPath extends FileOutputStream {
private final File mFile;
@@ -1513,7 +1480,7 @@
}
}
- void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) {
+ void saveIconAndFixUpShortcutLocked(ShortcutPackage p, ShortcutInfo shortcut) {
if (shortcut.hasIconFile() || shortcut.hasIconResource() || shortcut.hasIconUri()) {
return;
}
@@ -1521,7 +1488,7 @@
final long token = injectClearCallingIdentity();
try {
// Clear icon info on the shortcut.
- removeIconLocked(shortcut);
+ p.removeIcon(shortcut);
final Icon icon = shortcut.getIcon();
if (icon == null) {
@@ -1560,8 +1527,7 @@
// just in case.
throw ShortcutInfo.getInvalidIconException();
}
- mShortcutBitmapSaver.saveBitmapLocked(shortcut,
- maxIconDimension, mIconPersistFormat, mIconPersistQuality);
+ p.saveBitmap(shortcut, maxIconDimension, mIconPersistFormat, mIconPersistQuality);
} finally {
// Once saved, we won't use the original icon information, so null it out.
shortcut.clearIcon();
@@ -2110,7 +2076,7 @@
final boolean replacingIcon = (source.getIcon() != null);
if (replacingIcon) {
- removeIconLocked(target);
+ ps.removeIcon(target);
}
// Note copyNonNullFieldsFrom() does the "updatable with?" check too.
@@ -2118,7 +2084,7 @@
target.setTimestamp(injectCurrentTimeMillis());
if (replacingIcon) {
- saveIconAndFixUpShortcutLocked(target);
+ saveIconAndFixUpShortcutLocked(ps, target);
}
// When we're updating any resource related fields, re-extract the res
@@ -3463,7 +3429,7 @@
if (shortcutInfo == null) {
return null;
}
- return getShortcutIconParcelFileDescriptor(shortcutInfo);
+ return getShortcutIconParcelFileDescriptor(p, shortcutInfo);
}
}
@@ -3476,6 +3442,7 @@
Objects.requireNonNull(shortcutId, "shortcutId");
// Checks shortcuts in memory first
+ final ShortcutPackage p;
synchronized (mLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3483,8 +3450,7 @@
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave();
- final ShortcutPackage p = getUserShortcutsLocked(userId)
- .getPackageShortcutsIfExists(packageName);
+ p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
if (p == null) {
cb.complete(null);
return;
@@ -3492,24 +3458,23 @@
final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
if (shortcutInfo != null) {
- cb.complete(getShortcutIconParcelFileDescriptor(shortcutInfo));
+ cb.complete(getShortcutIconParcelFileDescriptor(p, shortcutInfo));
return;
}
}
// Otherwise check persisted shortcuts
- getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> {
- cb.complete(getShortcutIconParcelFileDescriptor(si));
- });
+ getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si ->
+ cb.complete(getShortcutIconParcelFileDescriptor(p, si)));
}
@Nullable
private ParcelFileDescriptor getShortcutIconParcelFileDescriptor(
- @Nullable final ShortcutInfo shortcutInfo) {
- if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
+ @Nullable final ShortcutPackage p, @Nullable final ShortcutInfo shortcutInfo) {
+ if (p == null || shortcutInfo == null || !shortcutInfo.hasIconFile()) {
return null;
}
- final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo);
+ final String path = p.getBitmapPathMayWait(shortcutInfo);
if (path == null) {
Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
return null;
@@ -4772,9 +4737,6 @@
}
pw.println();
- mShortcutBitmapSaver.dumpLocked(pw, " ");
-
- pw.println();
}
for (int i = 0; i < mUsers.size(); i++) {
@@ -5347,9 +5309,11 @@
}
}
- void waitForBitmapSaves() {
+ @VisibleForTesting
+ void waitForBitmapSavesForTest() {
synchronized (mLock) {
- mShortcutBitmapSaver.waitForAllSavesLocked();
+ forEachLoadedUserLocked(u ->
+ u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves));
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 75e18b5..b9fd2fd 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -401,6 +401,7 @@
private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
boolean forBackup) throws IOException, XmlPullParserException {
+ spi.waitForBitmapSaves();
if (forBackup) {
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0dabff8..bcdf429 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1430,6 +1430,8 @@
/**
* Returns a UserInfo object with the name filled in, for Owner and Guest, or the original
* if the name is already set.
+ *
+ * Note: Currently, the resulting name can be null if a user was truly created with a null name.
*/
private UserInfo userWithName(UserInfo orig) {
if (orig != null && orig.name == null) {
@@ -1638,7 +1640,7 @@
}
@Override
- public String getUserName() {
+ public @NonNull String getUserName() {
final int callingUid = Binder.getCallingUid();
if (!hasQueryOrCreateUsersPermission()
&& !hasPermissionGranted(
@@ -1649,7 +1651,10 @@
final int userId = UserHandle.getUserId(callingUid);
synchronized (mUsersLock) {
UserInfo userInfo = userWithName(getUserInfoLU(userId));
- return userInfo == null ? "" : userInfo.name;
+ if (userInfo != null && userInfo.name != null) {
+ return userInfo.name;
+ }
+ return "";
}
}
@@ -4165,7 +4170,7 @@
* @return the converted user, or {@code null} if no pre-created user could be converted.
*/
private @Nullable UserInfo convertPreCreatedUserIfPossible(String userType,
- @UserInfoFlag int flags, String name, @Nullable Object token) {
+ @UserInfoFlag int flags, @Nullable String name, @Nullable Object token) {
final UserData preCreatedUserData;
synchronized (mUsersLock) {
preCreatedUserData = getPreCreatedUserLU(userType);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 2277d8a..7be83b0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -2816,14 +2816,12 @@
}
// Remove review flag as it is not necessary anymore
- // TODO(b/227186603) re-enable check for notification permission once
- // droidfood state has been cleared
- //if (!NOTIFICATION_PERMISSIONS.contains(perm)) {
+ if (!NOTIFICATION_PERMISSIONS.contains(perm)) {
if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
wasChanged = true;
}
- //}
+ }
if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0
&& !isPermissionSplitFromNonRuntime(permName,
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index 2d2bad2..5964fa4 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -68,7 +68,7 @@
*
* @hide
*/
-public final class LowPowerStandbyController {
+public class LowPowerStandbyController {
private static final String TAG = "LowPowerStandbyController";
private static final boolean DEBUG = false;
private static final boolean DEFAULT_ACTIVE_DURING_MAINTENANCE = false;
@@ -173,7 +173,9 @@
mSettingsObserver = new SettingsObserver(mHandler);
}
- void systemReady() {
+ /** Call when system services are ready */
+ @VisibleForTesting
+ public void systemReady() {
final Resources resources = mContext.getResources();
synchronized (mLock) {
mSupportedConfig = resources.getBoolean(
@@ -435,7 +437,9 @@
}
}
- void setActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ /** Set whether Low Power Standby should be active during doze maintenance mode. */
+ @VisibleForTesting
+ public void setActiveDuringMaintenance(boolean activeDuringMaintenance) {
synchronized (mLock) {
if (!mSupportedConfig) {
Slog.w(TAG, "Low Power Standby settings cannot be changed "
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 0834417..3c779f3 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -727,7 +727,8 @@
return false;
}
- if (mKeyguardManager != null && mKeyguardManager.isDeviceLocked(userId)) {
+ if (requiresAuthentication() && mKeyguardManager != null
+ && mKeyguardManager.isDeviceLocked(userId)) {
Log.i(TAG, "Can't change mic/cam toggle while device is locked");
return false;
}
@@ -993,6 +994,13 @@
}
@Override
+ public boolean requiresAuthentication() {
+ enforceObserveSensorPrivacyPermission();
+ return mContext.getResources()
+ .getBoolean(R.bool.config_sensorPrivacyRequiresAuthentication);
+ }
+
+ @Override
public void showSensorUseDialog(int sensor) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Can only be called by the system uid");
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b685572..d48f263 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1650,13 +1650,11 @@
}
@Override
- public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
- boolean isBubbleSuppressed) {
+ public void onBubbleMetadataFlagChanged(String key, int flags) {
enforceStatusBarService();
final long identity = Binder.clearCallingIdentity();
try {
- mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed,
- isBubbleSuppressed);
+ mNotificationDelegate.onBubbleMetadataFlagChanged(key, flags);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 732cb71..791d193 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5096,7 +5096,11 @@
final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS);
if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn())
&& (appTransition.isTransitionSet()
- || (recentsAnimating && !isActivityTypeHome()))) {
+ || (recentsAnimating && !isActivityTypeHome()))
+ // If the visibility change during enter PIP, we don't want to include it in app
+ // transition to affect the animation theme, because the Pip organizer will animate
+ // the entering PIP instead.
+ && !mWaitForEnteringPinnedMode) {
if (visible) {
displayContent.mOpeningApps.add(this);
mEnteringAnimation = true;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 34c083a..b97ee7e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1807,6 +1807,10 @@
// Check if starting activity on given task or on a new task is allowed.
int startResult = isAllowedToStart(r, newTask, targetTask);
if (startResult != START_SUCCESS) {
+ if (r.resultTo != null) {
+ r.resultTo.sendResult(INVALID_UID, r.resultWho, r.requestCode, RESULT_CANCELED,
+ null /* data */, null /* dataGrants */);
+ }
return startResult;
}
@@ -1976,13 +1980,9 @@
mPreferredWindowingMode = mLaunchParams.mWindowingMode;
}
- private int isAllowedToStart(ActivityRecord r, boolean newTask, Task targetTask) {
- if (mStartActivity.packageName == null) {
- if (mStartActivity.resultTo != null) {
- mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
- mStartActivity.requestCode, RESULT_CANCELED,
- null /* data */, null /* dataGrants */);
- }
+ @VisibleForTesting
+ int isAllowedToStart(ActivityRecord r, boolean newTask, Task targetTask) {
+ if (r.packageName == null) {
ActivityOptions.abort(mOptions);
return START_CLASS_NOT_FOUND;
}
@@ -2005,8 +2005,7 @@
|| !targetTask.isUidPresent(mCallingUid)
|| (LAUNCH_SINGLE_INSTANCE == mLaunchMode && targetTask.inPinnedWindowingMode()));
- if (mRestrictedBgActivity && blockBalInTask
- && handleBackgroundActivityAbort(mStartActivity)) {
+ if (mRestrictedBgActivity && blockBalInTask && handleBackgroundActivityAbort(r)) {
Slog.e(TAG, "Abort background activity starts from " + mCallingUid);
return START_ABORTED;
}
@@ -2020,12 +2019,12 @@
if (!newTask) {
if (mService.getLockTaskController().isLockTaskModeViolation(targetTask,
isNewClearTask)) {
- Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+ Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
} else {
- if (mService.getLockTaskController().isNewTaskLockTaskModeViolation(mStartActivity)) {
- Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+ if (mService.getLockTaskController().isNewTaskLockTaskModeViolation(r)) {
+ Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 701fc94..cb65597 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,6 +68,7 @@
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -1142,13 +1143,17 @@
if (activity == null) {
continue;
}
+ if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Delaying app transition for recents animation to finish");
+ return false;
+ }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
activity, activity.allDrawn, activity.startingDisplayed,
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
-
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
return false;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ed1bbf8..2975a95 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4272,7 +4272,15 @@
setImeInputTarget(target);
mInsetsStateController.updateAboveInsetsState(mInsetsStateController
.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
- updateImeControlTarget();
+ // Force updating the IME parent when the IME control target has been updated to the
+ // remote target but updateImeParent not happen because ImeLayeringTarget and
+ // ImeInputTarget are different. Then later updateImeParent would be ignored when there
+ // is no new IME control target to change the IME parent.
+ final boolean forceUpdateImeParent = mImeControlTarget == mRemoteInsetsControlTarget
+ && (mInputMethodSurfaceParent != null
+ && !mInputMethodSurfaceParent.isSameSurface(
+ mImeWindowsContainer.getParent().mSurfaceControl));
+ updateImeControlTarget(forceUpdateImeParent);
}
// Unfreeze IME insets after the new target updated, in case updateAboveInsetsState may
// deliver unrelated IME insets change to the non-IME requester.
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 67dd89e..33cdd2e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -270,6 +270,22 @@
}
}
+ @Override
+ public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
+ synchronized (mService.mGlobalLock) {
+ mService.setMousePointerDisplayId(displayId);
+ if (displayId == Display.INVALID_DISPLAY) return;
+
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
+ + " that does not have a valid DisplayContent.");
+ return;
+ }
+ mService.restorePointerIconLocked(dc, x, y);
+ }
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index efe617d..2bae59a 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -19,7 +19,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
@@ -602,22 +601,12 @@
|| mDisplayContent.getAsyncRotationController() != null) {
return;
}
- boolean shouldTranslateNavBar = false;
- final boolean isDisplayLandscape =
- mDisplayContent.getConfiguration().orientation == ORIENTATION_LANDSCAPE;
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
final Task task = adapter.mTask;
- final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
- final boolean inSplitScreen = task.inSplitScreen();
- if (task.isActivityTypeHomeOrRecents()
- // Skip if the task is in split screen and in landscape.
- || (inSplitScreen && isDisplayLandscape)
- // Skip if the task is the top task in split screen.
- || (inSplitScreen && task.getBounds().top < adjacentTask.getBounds().top)) {
+ if (task.isActivityTypeHomeOrRecents()) {
continue;
}
- shouldTranslateNavBar = inSplitScreen;
mNavBarAttachedApp = task.getTopVisibleActivity();
break;
}
@@ -630,9 +619,7 @@
navWindow.mToken.cancelAnimation();
final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
- if (shouldTranslateNavBar) {
- navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
- }
+ navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
t.reparent(navSurfaceControl, mNavBarAttachedApp.getSurfaceControl());
t.show(navSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 83be73a..4068a97 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.util.TimeUtils.NANOS_PER_MS;
import static android.view.Choreographer.CALLBACK_TRAVERSAL;
import static android.view.Choreographer.getSfInstance;
@@ -26,14 +27,13 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
-import android.graphics.Canvas;
import android.graphics.Insets;
-import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.PowerManagerInternal;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Choreographer;
@@ -50,6 +50,8 @@
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.function.Supplier;
/**
@@ -83,6 +85,12 @@
private final PowerManagerInternal mPowerManagerInternal;
private boolean mApplyScheduled;
+ // Executor to perform the edge extension.
+ // With two threads because in practice we will want to extend two surfaces in one animation,
+ // in which case we want to be able to parallelize those two extensions to cut down latency in
+ // starting the animation.
+ private final ExecutorService mEdgeExtensionExecutor = Executors.newFixedThreadPool(2);
+
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
@@ -173,7 +181,7 @@
// We must wait for t to be committed since otherwise the leash doesn't have the
// windows we want to screenshot and extend as children.
- t.addTransactionCommittedListener(Runnable::run, () -> {
+ t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
final Transaction edgeExtensionCreationTransaction = new Transaction();
@@ -403,30 +411,17 @@
private void createExtensionSurface(SurfaceControl leash, Rect edgeBounds,
Rect extensionRect, int xPos, int yPos, String layerName,
Transaction startTransaction) {
- synchronized (mEdgeExtensionLock) {
- if (!mEdgeExtensions.containsKey(leash)) {
- // Animation leash has already been removed so we shouldn't perform any extension
- return;
- }
- createExtensionSurfaceLocked(leash, edgeBounds, extensionRect, xPos, yPos, layerName,
- startTransaction);
- }
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createExtensionSurface");
+ doCreateExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, layerName,
+ startTransaction);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- private void createExtensionSurfaceLocked(SurfaceControl surfaceToExtend, Rect edgeBounds,
+ private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds,
Rect extensionRect, int xPos, int yPos, String layerName,
Transaction startTransaction) {
- final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
- .setName(layerName)
- .setParent(surfaceToExtend)
- .setHidden(true)
- .setCallsite("DefaultTransitionHandler#startAnimation")
- .setOpaque(true)
- .setBufferSize(extensionRect.width(), extensionRect.height())
- .build();
-
SurfaceControl.LayerCaptureArgs captureArgs =
- new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+ new SurfaceControl.LayerCaptureArgs.Builder(leash /* surfaceToExtend */)
.setSourceCrop(edgeBounds)
.setFrameScale(1)
.setPixelFormat(PixelFormat.RGBA_8888)
@@ -437,31 +432,76 @@
SurfaceControl.captureLayers(captureArgs);
if (edgeBuffer == null) {
+ // The leash we are trying to screenshot may have been removed by this point, which is
+ // likely the reason for ending up with a null edgeBuffer, in which case we just want to
+ // return and do nothing.
Log.e("SurfaceAnimationRunner", "Failed to create edge extension - "
+ "edge buffer is null");
return;
}
- android.graphics.BitmapShader shader =
- new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
- android.graphics.Shader.TileMode.CLAMP,
- android.graphics.Shader.TileMode.CLAMP);
- final Paint paint = new Paint();
- paint.setShader(shader);
+ final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+ .setName(layerName)
+ .setHidden(true)
+ .setCallsite("DefaultTransitionHandler#startAnimation")
+ .setOpaque(true)
+ .setBufferSize(edgeBounds.width(), edgeBounds.height())
+ .build();
final Surface surface = new Surface(edgeExtensionLayer);
- Canvas c = surface.lockHardwareCanvas();
- c.drawRect(extensionRect, paint);
- surface.unlockCanvasAndPost(c);
+ surface.attachAndQueueBufferWithColorSpace(edgeBuffer.getHardwareBuffer(),
+ edgeBuffer.getColorSpace());
surface.release();
- startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
- startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
- startTransaction.setVisibility(edgeExtensionLayer, true);
+ final float scaleX = getScaleXForExtensionSurface(edgeBounds, extensionRect);
+ final float scaleY = getScaleYForExtensionSurface(edgeBounds, extensionRect);
- mEdgeExtensions.get(surfaceToExtend).add(edgeExtensionLayer);
+ synchronized (mEdgeExtensionLock) {
+ if (!mEdgeExtensions.containsKey(leash)) {
+ // The animation leash has already been removed, so we don't want to attach the
+ // edgeExtension layer and should immediately remove it instead.
+ startTransaction.remove(edgeExtensionLayer);
+ return;
+ }
+
+ startTransaction.setScale(edgeExtensionLayer, scaleX, scaleY);
+ startTransaction.reparent(edgeExtensionLayer, leash);
+ startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+ startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+ startTransaction.setVisibility(edgeExtensionLayer, true);
+
+ mEdgeExtensions.get(leash).add(edgeExtensionLayer);
+ }
}
+ private float getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
+ if (edgeBounds.width() == extensionRect.width()) {
+ // Top or bottom edge extension, no need to scale the X axis of the extension surface.
+ return 1;
+ }
+ if (edgeBounds.width() == 1) {
+ // Left or right edge extension, scale the surface to be the extensionRect's width.
+ return extensionRect.width();
+ }
+
+ throw new RuntimeException("Unexpected edgeBounds and extensionRect widths");
+ }
+
+ private float getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
+ if (edgeBounds.height() == extensionRect.height()) {
+ // Left or right edge extension, no need to scale the Y axis of the extension surface.
+ return 1;
+ }
+ if (edgeBounds.height() == 1) {
+ // Top or bottom edge extension, scale the surface to be the extensionRect's height.
+ return extensionRect.height();
+ }
+
+ throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
+ }
+
+
+
private static final class RunningAnimation {
final AnimationSpec mAnimSpec;
final SurfaceControl mLeash;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 718ce28..60c280c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -28,7 +28,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
@@ -1720,13 +1719,6 @@
&& (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
}
- /** Returns {@code true} if this task is currently in split-screen. */
- boolean inSplitScreen() {
- return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
- && getCreatedByOrganizerTask() != null
- && getCreatedByOrganizerTask().getAdjacentTaskFragment() != null;
- }
-
private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
return super.supportsSplitScreenWindowingMode()
&& mAtmService.mSupportsSplitScreenMultiWindow
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 1176182..ce406e4 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1163,20 +1163,21 @@
}
}
- // For a better split UX, If a task is launching from a created-by-organizer task, it should
- // be launched into the same created-by-organizer task as well. Unless, the candidate task
- // is already positioned in the split.
- Task preferredRootInSplit = sourceTask != null && sourceTask.inSplitScreen()
- ? sourceTask.getCreatedByOrganizerTask() : null;
- if (preferredRootInSplit != null) {
- if (candidateTask != null) {
- final Task candidateRoot = candidateTask.getCreatedByOrganizerTask();
- if (candidateRoot != null && candidateRoot != preferredRootInSplit
- && preferredRootInSplit == candidateRoot.getAdjacentTaskFragment()) {
- preferredRootInSplit = candidateRoot;
+ // If a task is launching from a created-by-organizer task, it should be launched into the
+ // same created-by-organizer task as well. Unless, the candidate task is already positioned
+ // in the another adjacent task.
+ if (sourceTask != null) {
+ Task launchTarget = sourceTask.getCreatedByOrganizerTask();
+ if (launchTarget != null && launchTarget.getAdjacentTaskFragment() != null) {
+ if (candidateTask != null) {
+ final Task candidateRoot = candidateTask.getCreatedByOrganizerTask();
+ if (candidateRoot != null && candidateRoot != launchTarget
+ && launchTarget == candidateRoot.getAdjacentTaskFragment()) {
+ launchTarget = candidateRoot;
+ }
}
+ return launchTarget;
}
- return preferredRootInSplit;
}
return null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c7bc513..a480c37 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1584,6 +1584,14 @@
return true;
}
+ void forAllWindowContainers(Consumer<WindowContainer> callback) {
+ callback.accept(this);
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ mChildren.get(i).forAllWindowContainers(callback);
+ }
+ }
+
/**
* For all windows at or below this container call the callback.
* @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and
@@ -3741,8 +3749,16 @@
}
// Otherwise this is the "root" of a synced subtree, so continue on to preparation.
}
+
// This container's situation has changed so we need to restart its sync.
- mSyncState = SYNC_STATE_NONE;
+ // We cannot reset the sync without a chance of a deadlock since it will request a new
+ // buffer from the app process. This could cause issues if the app has run out of buffers
+ // since the previous buffer was already synced and is still held in a transaction.
+ // Resetting syncState violates the policies outlined in BlastSyncEngine.md so for now
+ // disable this when shell transitions is disabled.
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ mSyncState = SYNC_STATE_NONE;
+ }
prepareSync();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7b77fd0..3906a19 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -118,6 +118,7 @@
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
@@ -3050,13 +3051,22 @@
}
}
+
void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
if (mRecentsAnimationController != null) {
final RecentsAnimationController controller = mRecentsAnimationController;
mRecentsAnimationController = null;
controller.cleanupAnimation(reorderMode);
- // TODO(mult-display): currently only default display support recents animation.
- getDefaultDisplayContentLocked().mAppTransition.updateBooster();
+ // TODO(multi-display): currently only default display support recents animation.
+ // Cancel any existing app transition animation running in the legacy transition
+ // framework.
+ final DisplayContent dc = getDefaultDisplayContentLocked();
+ dc.mAppTransition.freeze();
+ dc.forAllWindowContainers((wc) -> {
+ if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
+ wc.cancelAnimation();
+ }
+ });
}
}
@@ -7198,18 +7208,42 @@
private float mLatestMouseX;
private float mLatestMouseY;
- void updatePosition(float x, float y) {
+ /**
+ * The display that the pointer (mouse cursor) is currently shown on. This is updated
+ * directly by InputManagerService when the pointer display changes.
+ */
+ private int mPointerDisplayId = INVALID_DISPLAY;
+
+ /**
+ * Update the mouse cursor position as a result of a mouse movement.
+ * @return true if the position was successfully updated, false otherwise.
+ */
+ boolean updatePosition(int displayId, float x, float y) {
synchronized (this) {
mLatestEventWasMouse = true;
+
+ if (displayId != mPointerDisplayId) {
+ // The display of the position update does not match the display on which the
+ // mouse pointer is shown, so do not update the position.
+ return false;
+ }
mLatestMouseX = x;
mLatestMouseY = y;
+ return true;
+ }
+ }
+
+ void setPointerDisplayId(int displayId) {
+ synchronized (this) {
+ mPointerDisplayId = displayId;
}
}
@Override
public void onPointerEvent(MotionEvent motionEvent) {
if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
- updatePosition(motionEvent.getRawX(), motionEvent.getRawY());
+ updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
+ motionEvent.getRawY());
} else {
synchronized (this) {
mLatestEventWasMouse = false;
@@ -7219,6 +7253,7 @@
};
void updatePointerIcon(IWindow client) {
+ int pointerDisplayId;
float mouseX, mouseY;
synchronized(mMousePositionTracker) {
@@ -7227,6 +7262,7 @@
}
mouseX = mMousePositionTracker.mLatestMouseX;
mouseY = mMousePositionTracker.mLatestMouseY;
+ pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
}
synchronized (mGlobalLock) {
@@ -7243,6 +7279,10 @@
if (displayContent == null) {
return;
}
+ if (pointerDisplayId != displayContent.getDisplayId()) {
+ // Do not let the pointer icon be updated by a window on a different display.
+ return;
+ }
WindowState windowUnderPointer =
displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
if (windowUnderPointer != callingWin) {
@@ -7260,7 +7300,11 @@
void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
// Mouse position tracker has not been getting updates while dragging, update it now.
- mMousePositionTracker.updatePosition(latestX, latestY);
+ if (!mMousePositionTracker.updatePosition(
+ displayContent.getDisplayId(), latestX, latestY)) {
+ // The mouse position could not be updated, so ignore this request.
+ return;
+ }
WindowState windowUnderPointer =
displayContent.getTouchableWinAtPointLocked(latestX, latestY);
@@ -7284,6 +7328,10 @@
}
}
+ void setMousePointerDisplayId(int displayId) {
+ mMousePositionTracker.setPointerDisplayId(displayId);
+ }
+
/**
* Update a tap exclude region in the window identified by the provided id. Touches down on this
* region will not:
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index cd19f64..3b282aa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3639,13 +3639,11 @@
final int requested = mLastRequestedExclusionHeight[side];
final int granted = mLastGrantedExclusionHeight[side];
- final boolean inSplitScreen = getTask() != null && getTask().inSplitScreen();
-
FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED,
mAttrs.packageName, requested, requested - granted /* rejected */,
side + 1 /* Sides are 1-indexed in atoms.proto */,
(getConfiguration().orientation == ORIENTATION_LANDSCAPE),
- inSplitScreen, (int) duration);
+ false /* (deprecated param) inSplitscreen */, (int) duration);
}
private void initExclusionRestrictions() {
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 49a4021..93152f2 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -66,13 +66,13 @@
// Defines the maximum amount of VMAs we can send per process_madvise syscall.
// Currently this is set to UIO_MAXIOV which is the maximum segments allowed by
// iovec implementation used by process_madvise syscall
-#define MAX_VMAS_PER_BATCH UIO_MAXIOV
+#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV
// Maximum bytes that we can send per process_madvise syscall once this limit
// is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT
// limit is imposed by iovec implementation. However, if you want to use a smaller
-// limit, it has to be a page aligned value.
-#define MAX_BYTES_PER_BATCH MAX_RW_COUNT
+// limit, it has to be a page aligned value, otherwise, compaction would fail.
+#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT
// Selected a high enough number to avoid clashing with linux errno codes
#define ERROR_COMPACTION_CANCELLED -1000
@@ -83,181 +83,6 @@
// before starting next VMA batch
static std::atomic<bool> cancelRunningCompaction;
-// A VmaBatch represents a set of VMAs that can be processed
-// as VMAs are processed by client code it is expected that the
-// VMAs get consumed which means they are discarded as they are
-// processed so that the first element always is the next element
-// to be sent
-struct VmaBatch {
- struct iovec* vmas;
- // total amount of VMAs to reach the end of iovec
- int totalVmas;
- // total amount of bytes that are remaining within iovec
- uint64_t totalBytes;
-};
-
-// Advances the iterator by the specified amount of bytes.
-// This is used to remove already processed or no longer
-// needed parts of the batch.
-// Returns total bytes consumed
-int consumeBytes(VmaBatch& batch, uint64_t bytesToConsume) {
- int index = 0;
- if (CC_UNLIKELY(bytesToConsume) < 0) {
- LOG(ERROR) << "Cannot consume negative bytes for VMA batch !";
- return 0;
- }
-
- if (bytesToConsume > batch.totalBytes) {
- // Avoid consuming more bytes than available
- bytesToConsume = batch.totalBytes;
- }
-
- uint64_t bytesConsumed = 0;
- while (bytesConsumed < bytesToConsume) {
- if (CC_UNLIKELY(index >= batch.totalVmas)) {
- // reach the end of the batch
- return bytesConsumed;
- }
- if (CC_UNLIKELY(bytesConsumed + batch.vmas[index].iov_len > bytesToConsume)) {
- // this is the whole VMA that will be consumed
- break;
- }
- bytesConsumed += batch.vmas[index].iov_len;
- batch.totalBytes -= batch.vmas[index].iov_len;
- --batch.totalVmas;
- ++index;
- }
-
- // Move pointer to consume all the whole VMAs
- batch.vmas = batch.vmas + index;
-
- // Consume the rest of the bytes partially at last VMA in batch
- uint64_t bytesLeftToConsume = bytesToConsume - bytesConsumed;
- bytesConsumed += bytesLeftToConsume;
- if (batch.totalVmas > 0) {
- batch.vmas[0].iov_base = (void*)((uint64_t)batch.vmas[0].iov_base + bytesLeftToConsume);
- }
-
- return bytesConsumed;
-}
-
-// given a source of vmas this class will act as a factory
-// of VmaBatch objects and it will allow generating batches
-// until there are no more left in the source vector.
-// Note: the class does not actually modify the given
-// vmas vector, instead it iterates on it until the end.
-class VmaBatchCreator {
- const std::vector<Vma>* sourceVmas;
- // This is the destination array where batched VMAs will be stored
- // it gets encapsulated into a VmaBatch which is the object
- // meant to be used by client code.
- struct iovec* destVmas;
-
- // Parameters to keep track of the iterator on the source vmas
- int currentIndex_;
- uint64_t currentOffset_;
-
-public:
- VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec)
- : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {}
-
- int currentIndex() { return currentIndex_; }
- uint64_t currentOffset() { return currentOffset_; }
-
- // Generates a batch and moves the iterator on the source vmas
- // past the last VMA in the batch.
- // Returns true on success, false on failure
- bool createNextBatch(VmaBatch& batch) {
- if (currentIndex_ >= MAX_VMAS_PER_BATCH && currentIndex_ >= sourceVmas->size()) {
- return false;
- }
-
- const std::vector<Vma>& vmas = *sourceVmas;
- batch.vmas = destVmas;
- uint64_t totalBytesInBatch = 0;
- int indexInBatch = 0;
-
- // Add VMAs to the batch up until we consumed all the VMAs or
- // reached any imposed limit of VMAs per batch.
- while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) {
- uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_;
- uint64_t vmaSize = vmas[currentIndex_].end - vmaStart;
- if (CC_UNLIKELY(vmaSize == 0)) {
- // No more bytes to batch for this VMA, move to next one
- // this only happens if a batch partially consumed bytes
- // and offset landed at exactly the end of a vma
- continue;
- }
- batch.vmas[indexInBatch].iov_base = (void*)vmaStart;
- uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch;
-
- if (vmaSize >= bytesAvailableInBatch) {
- // VMA would exceed the max available bytes in batch
- // clamp with available bytes and finish batch.
- vmaSize = bytesAvailableInBatch;
- currentOffset_ += bytesAvailableInBatch;
- }
-
- batch.vmas[indexInBatch].iov_len = vmaSize;
- totalBytesInBatch += vmaSize;
-
- ++indexInBatch;
- if (totalBytesInBatch >= MAX_BYTES_PER_BATCH) {
- // Reached max bytes quota so this marks
- // the end of the batch
- break;
- }
-
- // Fully finished current VMA, move to next one
- currentOffset_ = 0;
- ++currentIndex_;
- }
- // Vmas where fully filled and we are past the last filled index.
- batch.totalVmas = indexInBatch;
- batch.totalBytes = totalBytesInBatch;
- return true;
- }
-};
-
-// Madvise a set of VMAs given in a batch for a specific process
-// The total number of bytes successfully madvised will be set on
-// outBytesProcessed.
-// Returns 0 on success and standard linux -errno code returned by
-// process_madvise on failure
-int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType,
- uint64_t* outBytesProcessed) {
- if (batch.totalVmas == 0) {
- // No VMAs in Batch, skip.
- *outBytesProcessed = 0;
- return 0;
- }
-
- ATRACE_BEGIN(StringPrintf("Madvise %d: %d VMAs", madviseType, batch.totalVmas).c_str());
- uint64_t bytesProcessedInSend =
- process_madvise(pidfd, batch.vmas, batch.totalVmas, madviseType, 0);
- ATRACE_END();
-
- if (CC_UNLIKELY(bytesProcessedInSend == -1)) {
- bytesProcessedInSend = 0;
- if (errno != EINVAL) {
- // Forward irrecoverable errors and bail out compaction
- *outBytesProcessed = 0;
- return -errno;
- }
- }
-
- if (bytesProcessedInSend < batch.totalBytes) {
- // Did not process all the bytes requested
- // skip last page which likely failed
- bytesProcessedInSend += PAGE_SIZE;
- }
-
- bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend);
-
- *outBytesProcessed = bytesProcessedInSend;
- return 0;
-}
-
// Legacy method for compacting processes, any new code should
// use compactProcess instead.
static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
@@ -271,6 +96,8 @@
// If any VMA fails compaction due to -EINVAL it will be skipped and continue.
// However, if it fails for any other reason, it will bail out and forward the error
static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
+ static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION];
+
if (vmas.empty()) {
return 0;
}
@@ -281,16 +108,13 @@
return -errno;
}
- struct iovec destVmas[MAX_VMAS_PER_BATCH];
-
- VmaBatch batch;
- VmaBatchCreator batcher(&vmas, destVmas);
-
int64_t totalBytesProcessed = 0;
- while (batcher.createNextBatch(batch)) {
- uint64_t bytesProcessedInSend;
- do {
+ int64_t vmaOffset = 0;
+ for (int iVma = 0; iVma < vmas.size();) {
+ uint64_t bytesSentToCompact = 0;
+ int iVec = 0;
+ while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) {
if (CC_UNLIKELY(cancelRunningCompaction.load())) {
// There could be a significant delay between when a compaction
// is requested and when it is handled during this time our
@@ -300,13 +124,50 @@
StringPrintf("Cancelled compaction for %d", pid).c_str());
return ERROR_COMPACTION_CANCELLED;
}
- int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend);
- if (error < 0) {
- // Returns standard linux errno code
- return error;
+
+ uint64_t vmaStart = vmas[iVma].start + vmaOffset;
+ uint64_t vmaSize = vmas[iVma].end - vmaStart;
+ if (vmaSize == 0) {
+ goto next_vma;
}
- totalBytesProcessed += bytesProcessedInSend;
- } while (batch.totalBytes > 0);
+ vmasToKernel[iVec].iov_base = (void*)vmaStart;
+ if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) {
+ // Exceeded the max bytes that could be sent, so clamp
+ // the end to avoid exceeding limit and issue compaction
+ vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact;
+ }
+
+ vmasToKernel[iVec].iov_len = vmaSize;
+ bytesSentToCompact += vmaSize;
+ ++iVec;
+ if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) {
+ // Ran out of bytes within iovec, dispatch compaction.
+ vmaOffset += vmaSize;
+ break;
+ }
+
+ next_vma:
+ // Finished current VMA, and have more bytes remaining
+ vmaOffset = 0;
+ ++iVma;
+ }
+
+ ATRACE_BEGIN(StringPrintf("Compact %d VMAs", iVec).c_str());
+ auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0);
+ ATRACE_END();
+
+ if (CC_UNLIKELY(bytesProcessed == -1)) {
+ if (errno == EINVAL) {
+ // This error is somewhat common due to an unevictable VMA if this is
+ // the case silently skip the bad VMA and continue compacting the rest.
+ continue;
+ } else {
+ // Forward irrecoverable errors and bail out compaction
+ return -errno;
+ }
+ }
+
+ totalBytesProcessed += bytesProcessed;
}
return totalBytesProcessed;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3c5ebe7..32adac7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
jmethodID checkInjectEventsPermission;
+ jmethodID onPointerDisplayIdChanged;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -120,7 +121,6 @@
jmethodID getLongPressTimeout;
jmethodID getPointerLayer;
jmethodID getPointerIcon;
- jmethodID getPointerDisplayId;
jmethodID getKeyboardLayoutOverlay;
jmethodID getDeviceAlias;
jmethodID getTouchCalibrationForInputDevice;
@@ -277,6 +277,7 @@
void setFocusedDisplay(int32_t displayId);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
+ void setPointerDisplayId(int32_t displayId);
void setPointerSpeed(int32_t speed);
void setPointerAcceleration(float acceleration);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
@@ -288,7 +289,6 @@
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
void setCustomPointerIcon(const SpriteIcon& icon);
void setMotionClassifierEnabled(bool enabled);
- void notifyPointerDisplayIdChanged();
/* --- InputReaderPolicyInterface implementation --- */
@@ -346,6 +346,7 @@
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId);
virtual int32_t getDefaultPointerIconId();
virtual int32_t getCustomPointerIconId();
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
private:
sp<InputManagerInterface> mInputManager;
@@ -394,7 +395,6 @@
void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
- int32_t getPointerDisplayId();
sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
@@ -498,13 +498,9 @@
}
}
- // Get the preferred pointer controller displayId.
- int32_t pointerDisplayId = getPointerDisplayId();
-
{ // acquire lock
AutoMutex _l(mLock);
mLocked.viewports = viewports;
- mLocked.pointerDisplayId = pointerDisplayId;
std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
if (controller != nullptr) {
controller->onDisplayViewportsUpdated(mLocked.viewports);
@@ -666,15 +662,12 @@
return controller;
}
-int32_t NativeInputManager::getPointerDisplayId() {
+void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
+ float yPos) {
JNIEnv* env = jniEnv();
- jint pointerDisplayId = env->CallIntMethod(mServiceObj,
- gServiceClassInfo.getPointerDisplayId);
- if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
- pointerDisplayId = ADISPLAY_ID_DEFAULT;
- }
-
- return pointerDisplayId;
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
+ xPos, yPos);
+ checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
}
sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
@@ -1032,6 +1025,22 @@
: InactivityTimeout::NORMAL);
}
+void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.pointerDisplayId == displayId) {
+ return;
+ }
+
+ ALOGI("Setting pointer display id to %d.", displayId);
+ mLocked.pointerDisplayId = displayId;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
AutoMutex _l(mLock);
@@ -1494,18 +1503,6 @@
mInputManager->getClassifier().setMotionClassifierEnabled(enabled);
}
-void NativeInputManager::notifyPointerDisplayIdChanged() {
- int32_t pointerDisplayId = getPointerDisplayId();
-
- { // acquire lock
- AutoMutex _l(mLock);
- mLocked.pointerDisplayId = pointerDisplayId;
- } // release lock
-
- mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-}
-
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2199,11 +2196,6 @@
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
- NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->notifyPointerDisplayIdChanged();
-}
-
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
jint displayId, jboolean isEligible) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2321,6 +2313,11 @@
im->getInputManager()->getDispatcher().cancelCurrentTouch();
}
+static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setPointerDisplayId(displayId);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2393,7 +2390,6 @@
{"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
- {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
{"setDisplayEligibilityForPointerCapture", "(IZ)V",
(void*)nativeSetDisplayEligibilityForPointerCapture},
{"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
@@ -2403,6 +2399,7 @@
{"disableSensor", "(II)V", (void*)nativeDisableSensor},
{"flushSensor", "(II)Z", (void*)nativeFlushSensor},
{"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
+ {"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
};
#define FIND_CLASS(var, className) \
@@ -2498,6 +2495,9 @@
GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
"checkInjectEventsPermission", "(II)Z");
+ GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
+ "(IFF)V");
+
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
@@ -2537,9 +2537,6 @@
GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
"getPointerIcon", "(I)Landroid/view/PointerIcon;");
- GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
- "getPointerDisplayId", "()I");
-
GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
"getKeyboardLayoutOverlay",
"(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 8aa9f60..994a767 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -238,7 +238,7 @@
}
}
- // called from Device.close()
+ // called from Device.closeLocked()
public void removeDeviceConnection(DeviceConnection connection) {
mDeviceConnections.remove(connection.getToken());
if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
@@ -294,12 +294,6 @@
}
for (DeviceConnection connection : mDeviceConnections.values()) {
- if (connection.getDevice().getDeviceInfo().getType()
- == MidiDeviceInfo.TYPE_USB) {
- synchronized (mUsbMidiLock) {
- removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
- }
- }
connection.getDevice().removeDeviceConnection(connection);
}
}
@@ -541,6 +535,13 @@
synchronized (mDeviceConnections) {
mDeviceConnections.remove(connection);
+ if (connection.getDevice().getDeviceInfo().getType()
+ == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+ }
+ }
+
if (mDeviceConnections.size() == 0 && mServiceConnection != null) {
mContext.unbindService(mServiceConnection);
mServiceConnection = null;
@@ -559,6 +560,12 @@
public void closeLocked() {
synchronized (mDeviceConnections) {
for (DeviceConnection connection : mDeviceConnections) {
+ if (connection.getDevice().getDeviceInfo().getType()
+ == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+ }
+ }
connection.getClient().removeDeviceConnection(connection);
}
mDeviceConnections.clear();
@@ -1401,6 +1408,8 @@
String deviceName = extractUsbDeviceName(name);
String tagName = extractUsbDeviceTag(name);
+ Log.i(TAG, "Checking " + deviceName + " " + tagName);
+
// Only one MIDI 2.0 device can be used at once.
// Multiple MIDI 1.0 devices can be used at once.
if (mUsbMidiUniversalDeviceInUse.contains(deviceName)
@@ -1420,6 +1429,8 @@
String deviceName = extractUsbDeviceName(name);
String tagName = extractUsbDeviceTag(name);
+ Log.i(TAG, "Adding " + deviceName + " " + tagName);
+
if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
mUsbMidiUniversalDeviceInUse.add(deviceName);
} else if ((tagName).equals(MIDI_LEGACY_STRING)) {
@@ -1437,6 +1448,8 @@
String deviceName = extractUsbDeviceName(name);
String tagName = extractUsbDeviceTag(name);
+ Log.i(TAG, "Removing " + deviceName + " " + tagName);
+
if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
mUsbMidiUniversalDeviceInUse.remove(deviceName);
} else if ((tagName).equals(MIDI_LEGACY_STRING)) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 2f68306..8d59dce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -30,6 +30,7 @@
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
@@ -1591,6 +1592,69 @@
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoOne_TreatLikeVisFGS() {
+ final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+ final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
+ MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
+ client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ client2.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+
+ final ServiceRecord s1 = bindService(app1, client1, null,
+ Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class));
+ final ServiceRecord s2 = bindService(app2, client2, null,
+ Context.BIND_IMPORTANT, mock(IBinder.class));
+
+ sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+ assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
+ SCHED_GROUP_DEFAULT);
+
+ bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+ mock(IBinder.class));
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
+ SCHED_GROUP_DEFAULT);
+
+ s1.getConnections().clear();
+ s2.getConnections().clear();
+ client1.mState.setMaxAdj(UNKNOWN_ADJ);
+ client2.mState.setMaxAdj(UNKNOWN_ADJ);
+ client1.mServices.setHasForegroundServices(true, 0);
+ client2.mState.setHasOverlayUi(true);
+
+ bindService(app1, client1, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+ mock(IBinder.class));
+ bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+ mock(IBinder.class));
+
+ sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+ assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+
+ client2.mState.setHasOverlayUi(false);
+ doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+ doReturn(client2).when(sService).getTopApp();
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_UidIdle_StopService() {
final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index b4bb04d..77cbb3a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,22 +19,22 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.server.LocalServices;
import org.junit.Before;
@@ -44,7 +44,8 @@
import org.mockito.MockitoAnnotations;
@Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class InputControllerTest {
@Mock
@@ -56,13 +57,16 @@
@Mock
private IInputManager mIInputManagerMock;
+ private InputManagerMockHelper mInputManagerMockHelper;
private InputController mInputController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mInputManagerMockHelper = new InputManagerMockHelper(
+ TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
- doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+ doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
@@ -72,10 +76,10 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- InputManager.resetInstance(mIInputManagerMock);
- doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
- doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
- mInputController = new InputController(new Object(), mNativeWrapperMock);
+ // Allow virtual devices to be created on the looper thread for testing.
+ final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+ mInputController = new InputController(new Object(), mNativeWrapperMock,
+ new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
}
@Test
@@ -83,6 +87,7 @@
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
+ verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mInputController.unregisterInputDevice(deviceToken);
@@ -95,10 +100,12 @@
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
+ verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
final IBinder deviceToken2 = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
/* displayId= */ 2);
+ verify(mNativeWrapperMock, times(2)).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
mInputController.unregisterInputDevice(deviceToken);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
new file mode 100644
index 0000000..aa2d97e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/**
+ * A test utility class used to share the logic for setting up {@link InputManager}'s callback for
+ * when a virtual input device being added.
+ */
+class InputManagerMockHelper {
+ private final TestableLooper mTestableLooper;
+ private final InputController.NativeWrapper mNativeWrapperMock;
+ private final IInputManager mIInputManagerMock;
+ private final List<InputDevice> mDevices = new ArrayList<>();
+ private IInputDevicesChangedListener mDevicesChangedListener;
+
+ InputManagerMockHelper(TestableLooper testableLooper,
+ InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
+ throws Exception {
+ mTestableLooper = testableLooper;
+ mNativeWrapperMock = nativeWrapperMock;
+ mIInputManagerMock = iInputManagerMock;
+
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
+ anyString(), anyInt(), anyInt(), anyString());
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
+ anyString(), anyInt(), anyInt(), anyString());
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
+ anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt());
+
+ doAnswer(inv -> {
+ mDevicesChangedListener = inv.getArgument(0);
+ return null;
+ }).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull());
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+ doAnswer(inv -> mDevices.get(inv.getArgument(0)))
+ .when(mIInputManagerMock).getInputDevice(anyInt());
+ doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+ doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
+
+ // Set a new instance of InputManager for testing that uses the IInputManager mock as the
+ // interface to the server.
+ InputManager.resetInstance(mIInputManagerMock);
+ }
+
+ private Void handleNativeOpenInputDevice(InvocationOnMock inv) {
+ Objects.requireNonNull(mDevicesChangedListener,
+ "InputController did not register an InputDevicesChangedListener.");
+ // We only use a subset of the fields of InputDevice in InputController.
+ final InputDevice device = new InputDevice(mDevices.size() /*id*/, 1 /*generation*/, 0,
+ inv.getArgument(0) /*name*/, inv.getArgument(1) /*vendorId*/,
+ inv.getArgument(2) /*productId*/, inv.getArgument(3) /*descriptor*/,
+ true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/,
+ null /*keyCharacterMap*/, false /*hasVibrator*/, false /*hasMic*/,
+ false /*hasButtonUnderPad*/, false /*hasSensor*/, false /*hasBattery*/);
+ mDevices.add(device);
+ try {
+ mDevicesChangedListener.onInputDevicesChanged(
+ mDevices.stream().flatMapToInt(
+ d -> IntStream.of(d.getId(), d.getGeneration())).toArray());
+ } catch (RemoteException ignored) {
+ }
+ // Process the device added notification.
+ mTestableLooper.processAllMessages();
+ return null;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 808f8c2c..cbb9fd7 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -54,6 +54,7 @@
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
@@ -118,6 +119,7 @@
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private Context mContext;
+ private InputManagerMockHelper mInputManagerMockHelper;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
private AssociationInfo mAssociationInfo;
@@ -146,6 +148,8 @@
private IAudioConfigChangedCallback mConfigChangedCallback;
@Mock
private ApplicationInfo mApplicationInfoMock;
+ @Mock
+ IInputManager mIInputManagerMock;
private ArraySet<ComponentName> getBlockedActivities() {
ArraySet<ComponentName> blockedActivities = new ArraySet<>();
@@ -170,13 +174,13 @@
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+ doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
@@ -199,7 +203,13 @@
new Handler(TestableLooper.get(this).getLooper()));
when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
- mInputController = new InputController(new Object(), mNativeWrapperMock);
+ mInputManagerMockHelper = new InputManagerMockHelper(
+ TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
+ // Allow virtual devices to be created on the looper thread for testing.
+ final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+ mInputController = new InputController(new Object(), mNativeWrapperMock,
+ new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
+
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
new file mode 100644
index 0000000..cb97c9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.display.DisplayViewport
+import android.hardware.input.InputManagerInternal
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import androidx.test.InstrumentationRegistry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests for {@link InputManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:InputManagerServiceTests
+ */
+@Presubmit
+class InputManagerServiceTests {
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var native: NativeInputManagerService
+
+ @Mock
+ private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
+
+ private lateinit var service: InputManagerService
+ private lateinit var localService: InputManagerInternal
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+
+ @Before
+ fun setup() {
+ context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+ testLooper = TestLooper()
+ service =
+ InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
+ override fun getNativeService(
+ service: InputManagerService?
+ ): NativeInputManagerService {
+ return native
+ }
+
+ override fun registerLocalService(service: InputManagerInternal?) {
+ localService = service!!
+ }
+ })
+ assertTrue("Local service must be registered", this::localService.isInitialized)
+ service.setWindowManagerCallbacks(wmCallbacks)
+ }
+
+ @Test
+ fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
+ val displayId = 123
+ `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
+ val viewports = listOf<DisplayViewport>()
+ localService.setDisplayViewports(viewports)
+ verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
+ verify(native).setPointerDisplayId(displayId)
+
+ val x = 42f
+ val y = 314f
+ service.onPointerDisplayIdChanged(displayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId() {
+ // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+ // until the native callback happens.
+ var countDownLatch = CountDownLatch(1)
+ val overrideDisplayId = 123
+ Thread {
+ assertTrue("Setting virtual pointer display should succeed",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(overrideDisplayId)
+
+ // Ensure that setting the same override again succeeds immediately.
+ assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+
+ // Ensure that we did not query WM for the pointerDisplayId when setting the override
+ verify(wmCallbacks, never()).pointerDisplayId
+
+ // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
+ // pointer displayId and the calling thread is blocked until the native callback happens.
+ countDownLatch = CountDownLatch(1)
+ val pointerDisplayId = 42
+ `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
+ Thread {
+ assertTrue("Unsetting virtual mouse pointer displayId should succeed",
+ localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Unsetting virtual mouse pointer displayId should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(pointerDisplayId)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+ // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+ // until the native callback happens.
+ val countDownLatch = CountDownLatch(1)
+ val overrideDisplayId = 123
+ Thread {
+ assertFalse("Setting virtual pointer display should be unsuccessful",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ // Assume the native callback updates the pointerDisplayId to the incorrect value.
+ service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(overrideDisplayId)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId_competingRequests() {
+ val firstRequestSyncLatch = CountDownLatch(1)
+ doAnswer {
+ firstRequestSyncLatch.countDown()
+ }.`when`(native).setPointerDisplayId(anyInt())
+
+ val firstRequestLatch = CountDownLatch(1)
+ val firstOverride = 123
+ Thread {
+ assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
+ localService.setVirtualMousePointerDisplayId(firstOverride))
+ firstRequestLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+ assertTrue("Wait for first thread's request should succeed",
+ firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val secondRequestLatch = CountDownLatch(1)
+ val secondOverride = 42
+ Thread {
+ assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
+ localService.setVirtualMousePointerDisplayId(secondOverride))
+ secondRequestLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual mouse pointer should block",
+ secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ // Assume the native callback updates directly to the second request.
+ service.onPointerDisplayIdChanged(secondOverride, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
+ assertTrue("Native callback unblocks first thread",
+ firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+ assertTrue("Native callback unblocks second thread",
+ secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native, times(2)).setPointerDisplayId(anyInt())
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
new file mode 100644
index 0000000..f330017
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.logcat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.ContextWrapper;
+import android.os.ILogd;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.LocalServices;
+import com.android.server.logcat.LogcatManagerService.Injector;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Supplier;
+
+/**
+ * Tests for {@link com.android.server.logcat.LogcatManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:LogcatManagerServiceTest
+ */
+@SuppressWarnings("GuardedBy")
+public class LogcatManagerServiceTest {
+ private static final String APP1_PACKAGE_NAME = "app1";
+ private static final int APP1_UID = 10001;
+ private static final int APP1_GID = 10001;
+ private static final int APP1_PID = 10001;
+ private static final String APP2_PACKAGE_NAME = "app2";
+ private static final int APP2_UID = 10002;
+ private static final int APP2_GID = 10002;
+ private static final int APP2_PID = 10002;
+ private static final int FD1 = 10;
+ private static final int FD2 = 11;
+
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternalMock;
+ @Mock
+ private ILogd mLogdMock;
+
+ private LogcatManagerService mService;
+ private LogcatManagerService.LogcatManagerServiceInternal mLocalService;
+ private ContextWrapper mContextSpy;
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+ mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+
+ when(mActivityManagerInternalMock.getPackageNameByPid(APP1_PID)).thenReturn(
+ APP1_PACKAGE_NAME);
+ when(mActivityManagerInternalMock.getPackageNameByPid(APP2_PID)).thenReturn(
+ APP2_PACKAGE_NAME);
+
+ mService = new LogcatManagerService(mContextSpy, new Injector() {
+ @Override
+ protected Supplier<Long> createClock() {
+ return mClock::now;
+ }
+
+ @Override
+ protected Looper getLooper() {
+ return mTestLooper.getLooper();
+ }
+
+ @Override
+ protected ILogd getLogdService() {
+ return mLogdMock;
+ }
+ });
+ mLocalService = mService.getLocalService();
+ mService.onStart();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
+ @Test
+ public void test_RequestFromBackground_DeclinedWithoutPrompt() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_RECEIVER);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mContextSpy, never()).startActivityAsUser(any(), any());
+ }
+
+ @Test
+ public void test_RequestFromForegroundService_DeclinedWithoutPrompt() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mContextSpy, never()).startActivityAsUser(any(), any());
+ }
+
+ @Test
+ public void test_RequestFromTop_ShowsPrompt() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+ }
+
+ @Test
+ public void test_RequestFromTop_NoInteractionWithPrompt_DeclinesAfterTimeout()
+ throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+
+ advanceTime(LogcatManagerService.PENDING_CONFIRMATION_TIMEOUT_MILLIS);
+
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ }
+
+ @Test
+ public void test_RequestFromTop_Approved() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+ verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+
+ mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ }
+
+ @Test
+ public void test_RequestFromTop_Declined() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+ verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+
+ mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ }
+
+ @Test
+ public void test_RequestFromTop_MultipleRequestsApprovedTogether() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
+ mTestLooper.dispatchAll();
+ verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+ verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
+ verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
+
+ mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
+ verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
+ }
+
+ @Test
+ public void test_RequestFromTop_MultipleRequestsDeclinedTogether() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
+ mTestLooper.dispatchAll();
+ verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+ verify(mLogdMock, never()).approve(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
+ verify(mLogdMock, never()).decline(eq(APP1_UID), eq(APP1_GID), eq(APP1_PID), anyInt());
+
+ mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
+ }
+
+ @Test
+ public void test_RequestFromTop_Approved_DoesNotShowPromptAgain() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+ mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
+ mTestLooper.dispatchAll();
+
+ verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+ verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, times(1)).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
+ verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
+ }
+
+ @Test
+ public void test_RequestFromTop_Declined_DoesNotShowPromptAgain() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+ mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD2);
+ mTestLooper.dispatchAll();
+
+ verify(mContextSpy, times(1)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+ verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD1);
+ verify(mLogdMock, times(1)).decline(APP1_UID, APP1_GID, APP1_PID, FD2);
+ verify(mLogdMock, never()).approve(APP1_UID, APP1_GID, APP1_PID, FD2);
+ }
+
+ @Test
+ public void test_RequestFromTop_Approved_ShowsPromptForDifferentClient() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ when(mActivityManagerInternalMock.getUidProcessState(APP2_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+ mLocalService.approveAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ mService.getBinderService().startThread(APP2_UID, APP2_GID, APP2_PID, FD2);
+ mTestLooper.dispatchAll();
+
+ verify(mContextSpy, times(2)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+ verify(mLogdMock, never()).decline(APP2_UID, APP2_GID, APP2_PID, FD2);
+ verify(mLogdMock, never()).approve(APP2_UID, APP2_GID, APP2_PID, FD2);
+ }
+
+ @Test
+ public void test_RequestFromTop_Approved_ShowPromptAgainAfterTimeout() throws Exception {
+ when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn(
+ ActivityManager.PROCESS_STATE_TOP);
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+ mLocalService.declineAccessForClient(APP1_UID, APP1_PACKAGE_NAME);
+ mTestLooper.dispatchAll();
+
+ advanceTime(LogcatManagerService.STATUS_EXPIRATION_TIMEOUT_MILLIS);
+
+ mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1);
+ mTestLooper.dispatchAll();
+
+ verify(mContextSpy, times(2)).startActivityAsUser(any(), eq(UserHandle.SYSTEM));
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 4094377..fdf9354 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -1975,7 +1975,7 @@
if (si == null) {
return null;
}
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
return new File(si.getBitmapPath()).getName();
}
@@ -1984,7 +1984,7 @@
if (si == null) {
return null;
}
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
return new File(si.getBitmapPath()).getAbsolutePath();
}
@@ -2139,7 +2139,7 @@
}
protected boolean bitmapDirectoryExists(String packageName, int userId) {
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
final File path = new File(mService.getUserBitmapFilePath(userId), packageName);
return path.isDirectory();
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 411b521..867890f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1040,7 +1040,7 @@
dumpsysOnLogcat();
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
// Check files and directories.
// Package 3 has no bitmaps, so we don't create a directory.
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
@@ -1096,7 +1096,7 @@
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile();
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
"a.b.c", "d.e.f");
@@ -1111,7 +1111,7 @@
// The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3
// directory.
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3);
assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
@@ -1390,7 +1390,7 @@
.setIcon(Icon.createWithContentUri("test_uri"))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconUri());
@@ -1402,13 +1402,13 @@
.setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconResource());
assertEquals(R.drawable.black_32x32, si.getIconResourceId());
});
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
@@ -1419,7 +1419,7 @@
getTestContext().getResources(), R.drawable.black_64x64)))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconFile());
@@ -1437,7 +1437,7 @@
getTestContext().getResources(), R.drawable.black_64x64)))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconFile());
@@ -1451,7 +1451,7 @@
.setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconResource());
@@ -1463,7 +1463,7 @@
.setIcon(Icon.createWithContentUri("test_uri"))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconUri());
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index f2495e1..9ff7d69 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -71,7 +71,6 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerSaveState;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
@@ -144,6 +143,7 @@
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
@Mock private SystemPropertiesWrapper mSystemPropertiesMock;
@Mock private AppOpsManager mAppOpsManagerMock;
+ @Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
@Mock
private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
@@ -298,8 +298,7 @@
@Override
LowPowerStandbyController createLowPowerStandbyController(Context context,
Looper looper) {
- return new LowPowerStandbyController(context, mTestLooper.getLooper(),
- SystemClock::elapsedRealtime);
+ return mLowPowerStandbyControllerMock;
}
@Override
@@ -316,7 +315,6 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.removeServiceForTest(BatteryManagerInternal.class);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
FakeSettingsProvider.clearSettingsProvider();
}
@@ -1888,6 +1886,18 @@
assertThat(wakeLock.mDisabled).isFalse();
}
+ @Test
+ public void testSetLowPowerStandbyActiveDuringMaintenance_redirectsCallToNativeWrapper() {
+ createService();
+ startSystem();
+
+ mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(true);
+ verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(true);
+
+ mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(false);
+ verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(false);
+ }
+
private WakeLock acquireWakeLock(String tag, int flags) {
IBinder token = new Binder();
String packageName = "pkg.name";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 348e015..c0cd7a7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -198,6 +198,7 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
@@ -303,6 +304,8 @@
ActivityManagerInternal mAmi;
@Mock
private Looper mMainLooper;
+ @Mock
+ private NotificationManager mMockNm;
@Mock
IIntentSender pi1;
@@ -405,6 +408,7 @@
LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
+ mContext.addMockSystemService(NotificationManager.class, mMockNm);
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
@@ -7516,46 +7520,53 @@
}
@Test
- public void testOnBubbleNotificationSuppressionChanged() throws Exception {
+ public void testOnBubbleMetadataFlagChanged() throws Exception {
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
- // Bubble notification
+ // Post a bubble notification
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
-
+ // Set this so that the bubble can be suppressed
+ nr.getNotification().getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
waitForIdle();
- // NOT suppressed
+ // Check the flags
Notification n = mBinderService.getActiveNotifications(PKG)[0].getNotification();
assertFalse(n.getBubbleMetadata().isNotificationSuppressed());
+ assertFalse(n.getBubbleMetadata().getAutoExpandBubble());
+ assertFalse(n.getBubbleMetadata().isBubbleSuppressed());
+ assertTrue(n.getBubbleMetadata().isBubbleSuppressable());
// Reset as this is called when the notif is first sent
reset(mListeners);
- // Test: update suppression to true
- mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true,
- false);
+ // Test: change the flags
+ int flags = Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE;
+ flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+ flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+ flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
+ mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), flags);
waitForIdle();
// Check
n = mBinderService.getActiveNotifications(PKG)[0].getNotification();
- assertTrue(n.getBubbleMetadata().isNotificationSuppressed());
+ assertEquals(flags, n.getBubbleMetadata().getFlags());
// Reset to check again
reset(mListeners);
- // Test: update suppression to false
- mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false,
- false);
+ // Test: clear flags
+ mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), 0);
waitForIdle();
// Check
n = mBinderService.getActiveNotifications(PKG)[0].getNotification();
- assertFalse(n.getBubbleMetadata().isNotificationSuppressed());
+ assertEquals(0, n.getBubbleMetadata().getFlags());
}
@Test
@@ -9294,4 +9305,77 @@
// the notifyPostedLocked function is called twice.
verify(mListeners, times(2)).notifyPostedLocked(any(), any());
}
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_unknown() {
+ // Set up various possible states of the settings int and confirm whether or not the
+ // notification is shown as expected
+
+ // Initial state: default/unknown setting, make sure nothing happens
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
+ mService.maybeShowInitialReviewPermissionsNotification();
+ verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
+ }
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_shouldShow() {
+ // If state is SHOULD_SHOW, it ... should show
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+ mService.maybeShowInitialReviewPermissionsNotification();
+ verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+ any(Notification.class));
+ }
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_alreadyShown() {
+ // If state is either USER_INTERACTED or DISMISSED, we should not show this on boot
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ mService.maybeShowInitialReviewPermissionsNotification();
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+ mService.maybeShowInitialReviewPermissionsNotification();
+
+ verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
+ }
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_reshown() {
+ // If we have re-shown the notification and the user did not subsequently interacted with
+ // it, then make sure we show when trying on boot
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+ mService.maybeShowInitialReviewPermissionsNotification();
+ verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+ any(Notification.class));
+ }
+
+ @Test
+ public void testRescheduledReviewPermissionsNotification() {
+ // when rescheduled, the notification goes through the NotificationManagerInternal service
+ // this call doesn't need to know anything about previously scheduled state -- if called,
+ // it should send the notification & write the appropriate int to Settings
+ mInternalService.sendReviewPermissionsNotification();
+
+ // Notification should be sent
+ verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+ any(Notification.class));
+
+ // write STATE_RESHOWN to settings
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 63d7453..6d08959 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -289,6 +289,11 @@
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.build();
+
+ // make sure that the settings for review notification permissions are unset to begin with
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
}
private ByteArrayOutputStream writeXmlAndPurge(
@@ -656,6 +661,13 @@
verify(mPermissionHelper).setNotificationPermission(nMr1Expected);
verify(mPermissionHelper).setNotificationPermission(oExpected);
verify(mPermissionHelper).setNotificationPermission(pExpected);
+
+ // verify that we also write a state for review_permissions_notification to eventually
+ // show a notification
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
}
@Test
@@ -738,7 +750,7 @@
}
@Test
- public void testReadXml_newXml_noMigration() throws Exception {
+ public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -786,6 +798,70 @@
compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
verify(mPermissionHelper, never()).setNotificationPermission(any());
+
+ // verify that we do, however, write a state for review_permissions_notification to
+ // eventually show a notification, since this XML version is older than the notification
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
+ when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
+
+ String xml = "<ranking version=\"4\">\n"
+ + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
+ + "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n"
+ + "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
+ + "</package>\n"
+ + "<package name=\"" + PKG_O + "\" >\n"
+ + "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
+ + "</package>\n"
+ + "<package name=\"" + PKG_P + "\" >\n"
+ + "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+ NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW);
+ idn.setSound(null, new AudioAttributes.Builder()
+ .setUsage(USAGE_NOTIFICATION)
+ .setContentType(CONTENT_TYPE_SONIFICATION)
+ .setFlags(0)
+ .build());
+ idn.setShowBadge(false);
+ NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW);
+ ido.setShowBadge(true);
+ ido.setSound(null, new AudioAttributes.Builder()
+ .setUsage(USAGE_NOTIFICATION)
+ .setContentType(CONTENT_TYPE_SONIFICATION)
+ .setFlags(0)
+ .build());
+ NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH);
+ idp.lockFields(2);
+ idp.setSound(null, new AudioAttributes.Builder()
+ .setUsage(USAGE_NOTIFICATION)
+ .setContentType(CONTENT_TYPE_SONIFICATION)
+ .setFlags(0)
+ .build());
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
+
+ assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false));
+ compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false));
+ compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
+
+ verify(mPermissionHelper, never()).setNotificationPermission(any());
+
+ // this XML is new enough, we should not be attempting to show a notification or anything
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
}
@Test
@@ -903,7 +979,7 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
+ "<package name=\"com.example.o\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" uid=\"1111\">\n"
@@ -984,7 +1060,7 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1067,7 +1143,7 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1121,7 +1197,7 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
// Packages that exist solely in permissionhelper
+ "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
+ "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
new file mode 100644
index 0000000..5a4ce5da
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.rule.ServiceTestRule;
+
+import com.android.server.LocalServices;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+@RunWith(AndroidTestingRunner.class)
+public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
+ private ReviewNotificationPermissionsJobService mJobService;
+ private JobParameters mJobParams = new JobParameters(null,
+ ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
+ 0, false, false, null, null, null);
+
+ @Captor
+ ArgumentCaptor<JobInfo> mJobInfoCaptor;
+
+ @Mock
+ private JobScheduler mMockJobScheduler;
+
+ @Mock
+ private NotificationManagerInternal mMockNotificationManagerInternal;
+
+ @Rule
+ public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mJobService = new ReviewNotificationPermissionsJobService();
+ mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
+
+ // add NotificationManagerInternal to LocalServices
+ LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+ LocalServices.addService(NotificationManagerInternal.class,
+ mMockNotificationManagerInternal);
+ }
+
+ @Test
+ public void testScheduleJob() {
+ // if asked, the job doesn't currently exist yet
+ when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
+
+ final int rescheduleTimeMillis = 350; // arbitrary number
+
+ // attempt to schedule the job
+ ReviewNotificationPermissionsJobService.scheduleJob(mContext, rescheduleTimeMillis);
+ verify(mMockJobScheduler, times(1)).schedule(mJobInfoCaptor.capture());
+
+ // verify various properties of the job that is passed in to the job scheduler
+ JobInfo jobInfo = mJobInfoCaptor.getValue();
+ assertEquals(ReviewNotificationPermissionsJobService.JOB_ID, jobInfo.getId());
+ assertEquals(rescheduleTimeMillis, jobInfo.getMinLatencyMillis());
+ assertTrue(jobInfo.isPersisted()); // should continue after reboot
+ assertFalse(jobInfo.isPeriodic()); // one time
+ }
+
+ @Test
+ public void testOnStartJob() {
+ // the job need not be persisted after it does its work, so it'll return
+ // false
+ assertFalse(mJobService.onStartJob(mJobParams));
+
+ // verify that starting the job causes the notification to be sent
+ verify(mMockNotificationManagerInternal).sendReviewPermissionsNotification();
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java
new file mode 100644
index 0000000..12281a7
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ReviewNotificationPermissionsReceiverTest extends UiServiceTestCase {
+
+ // Simple mock class that just overrides the reschedule and cancel behavior so that it's easy
+ // to tell whether the receiver has sent requests to either reschedule or cancel the
+ // notification (or both).
+ private class MockReviewNotificationPermissionsReceiver
+ extends ReviewNotificationPermissionsReceiver {
+ boolean mCanceled = false;
+ boolean mRescheduled = false;
+
+ @Override
+ protected void cancelNotification(Context context) {
+ mCanceled = true;
+ }
+
+ @Override
+ protected void rescheduleNotification(Context context) {
+ mRescheduled = true;
+ }
+ }
+
+ private MockReviewNotificationPermissionsReceiver mReceiver;
+ private Intent mIntent;
+
+ @Before
+ public void setUp() {
+ mReceiver = new MockReviewNotificationPermissionsReceiver();
+ mIntent = new Intent(); // actions will be set in test cases
+ }
+
+ @Test
+ public void testReceive_remindMeLater_firstTime() {
+ // Test what happens when we receive a "remind me later" intent coming from
+ // a previously-not-interacted notification
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+ // set up Intent action
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+
+ // Upon receipt of the intent, the following things should happen:
+ // - notification rescheduled
+ // - notification explicitly canceled
+ // - settings state updated to indicate user has interacted
+ mReceiver.onReceive(mContext, mIntent);
+ assertTrue(mReceiver.mRescheduled);
+ assertTrue(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_remindMeLater_laterTimes() {
+ // Test what happens when we receive a "remind me later" intent coming from
+ // a previously-interacted notification that has been rescheduled
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+
+ // set up Intent action
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+
+ // Upon receipt of the intent, the following things should still happen
+ // regardless of the fact that the user has interacted before:
+ // - notification rescheduled
+ // - notification explicitly canceled
+ // - settings state still indicate user has interacted
+ mReceiver.onReceive(mContext, mIntent);
+ assertTrue(mReceiver.mRescheduled);
+ assertTrue(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_dismiss() {
+ // Test that dismissing the notification does *not* reschedule the notification,
+ // does cancel it, and writes that it has been dismissed to settings
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+ // set up Intent action
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
+
+ // send intent, watch what happens
+ mReceiver.onReceive(mContext, mIntent);
+ assertFalse(mReceiver.mRescheduled);
+ assertTrue(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_notificationCanceled_firstSwipe() {
+ // Test the basic swipe away case: the first time the user swipes the notification
+ // away, it will not have been interacted with yet, so make sure it's rescheduled
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+ // set up Intent action, would be called from notification's delete intent
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+ // send intent, make sure it gets:
+ // - rescheduled
+ // - not explicitly canceled, the notification was already canceled
+ // - noted that it's been interacted with
+ mReceiver.onReceive(mContext, mIntent);
+ assertTrue(mReceiver.mRescheduled);
+ assertFalse(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_notificationCanceled_secondSwipe() {
+ // Test the swipe away case for a rescheduled notification: in this case
+ // it should not be rescheduled anymore
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+
+ // set up Intent action, would be called from notification's delete intent
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+ // send intent, make sure it gets:
+ // - not rescheduled on the second+ swipe
+ // - not explicitly canceled, the notification was already canceled
+ // - mark as user interacted
+ mReceiver.onReceive(mContext, mIntent);
+ assertFalse(mReceiver.mRescheduled);
+ assertFalse(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_notificationCanceled_fromDismiss() {
+ // Test that if the notification delete intent is called due to us canceling
+ // the notification from the receiver, we don't do anything extra
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+
+ // set up Intent action, would be called from notification's delete intent
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+ // nothing should happen, nothing at all
+ mReceiver.onReceive(mContext, mIntent);
+ assertFalse(mReceiver.mRescheduled);
+ assertFalse(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 908de34..f59ec42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.Activity.RESULT_CANCELED;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CANCELED;
@@ -1297,6 +1298,22 @@
assertEquals(targetRecord.getLaunchIntoPipHostActivity(), sourceRecord);
}
+ @Test
+ public void testResultCanceledWhenNotAllowedStartingActivity() {
+ final ActivityStarter starter = prepareStarter(0, false);
+ final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build();
+ targetRecord.resultTo = sourceRecord;
+
+ // Abort the activity start and ensure the sourceRecord gets the result (RESULT_CANCELED).
+ spyOn(starter);
+ doReturn(START_ABORTED).when(starter).isAllowedToStart(any(), anyBoolean(), any());
+ startActivityInner(starter, targetRecord, sourceRecord, null /* options */,
+ null /* inTask */, null /* inTaskFragment */);
+ verify(sourceRecord).sendResult(anyInt(), any(), anyInt(), eq(RESULT_CANCELED), any(),
+ any());
+ }
+
private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
ActivityRecord source, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index eb6395b..3592158 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,8 +39,10 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -48,7 +50,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import android.graphics.Rect;
import android.os.Binder;
@@ -376,7 +380,7 @@
doReturn(false).when(dc).onDescendantOrientationChanged(any());
final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
dc, "exiting app");
- final ActivityRecord exitingActivity= exitingAppWindow.mActivityRecord;
+ final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
// Wait until everything in animation handler get executed to prevent the exiting window
// from being removed during WindowSurfacePlacer Traversal.
waitUntilHandlersIdle();
@@ -405,6 +409,41 @@
}
@Test
+ public void testDelayWhileRecents() {
+ final DisplayContent dc = createNewDisplay(Display.STATE_ON);
+ doReturn(false).when(dc).onDescendantOrientationChanged(any());
+ final Task task = createTask(dc);
+
+ // Simulate activity1 launches activity2.
+ final ActivityRecord activity1 = createActivityRecord(task);
+ activity1.setVisible(true);
+ activity1.mVisibleRequested = false;
+ activity1.allDrawn = true;
+ final ActivityRecord activity2 = createActivityRecord(task);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = true;
+ activity2.allDrawn = true;
+
+ dc.mClosingApps.add(activity1);
+ dc.mOpeningApps.add(activity2);
+ dc.prepareAppTransition(TRANSIT_OPEN);
+ assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
+
+ // Wait until everything in animation handler get executed to prevent the exiting window
+ // from being removed during WindowSurfacePlacer Traversal.
+ waitUntilHandlersIdle();
+
+ // Start recents
+ doReturn(true).when(task)
+ .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+
+ dc.mAppTransitionController.handleAppTransitionReady();
+
+ verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
+ verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
public void testGetAnimationStyleResId() {
// Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
// specifying window type.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 263c936..c5f785e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1184,6 +1184,31 @@
}
@Test
+ public void testComputeImeParent_remoteControlTarget() throws Exception {
+ final DisplayContent dc = mDisplayContent;
+ WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1");
+ WindowState app2 = createWindow(null, TYPE_BASE_APPLICATION, "app2");
+
+ dc.setImeLayeringTarget(app1);
+ dc.setImeInputTarget(app2);
+ dc.setRemoteInsetsController(createDisplayWindowInsetsController());
+ dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+ dc.getImeInputTarget().getWindowState().setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+
+ // Expect ImeParent is null since ImeLayeringTarget and ImeInputTarget are different.
+ assertNull(dc.computeImeParent());
+
+ // ImeLayeringTarget and ImeInputTarget are updated to the same.
+ dc.setImeInputTarget(app1);
+ assertEquals(dc.getImeTarget(IME_TARGET_LAYERING), dc.getImeInputTarget());
+
+ // The ImeParent should be the display.
+ assertEquals(dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent());
+ }
+
+ @Test
public void testInputMethodInputTarget_isClearedWhenWindowStateIsRemoved() throws Exception {
final DisplayContent dc = createNewDisplay();
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index bc8c639..22a6445 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,19 +5,6 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
}
]
},
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index f31cdcb..43fcc8f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -323,6 +323,16 @@
new RoleObserver(mContext.getMainExecutor());
}
+ void handleUserStop(String packageName, int userHandle) {
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ ComponentName curInteractor = getCurInteractor(userHandle);
+ if (curInteractor != null && packageName.equals(curInteractor.getPackageName())) {
+ Slog.d(TAG, "switchImplementation for user stop.");
+ switchImplementationIfNeededLocked(true);
+ }
+ }
+ }
+
@Override
public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
@NonNull Identity originatorIdentity, IBinder client) {
@@ -2071,6 +2081,7 @@
}
PackageMonitor mPackageMonitor = new PackageMonitor() {
+
@Override
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
if (DEBUG) Slog.d(TAG, "onHandleForceStop uid=" + uid + " doit=" + doit);
@@ -2103,11 +2114,17 @@
}
setCurInteractor(null, userHandle);
+ // TODO: should not reset null here. But even remove this line, the
+ // initForUser() still reset it because the interactor will be null. Keep
+ // it now but we should still need to fix it.
setCurRecognizer(null, userHandle);
resetCurAssistant(userHandle);
initForUser(userHandle);
switchImplementationIfNeededLocked(true);
+ // When resetting the interactor, the recognizer and the assistant settings
+ // value, we also need to reset the assistant role to keep the values
+ // consistent. Clear the assistant role will reset to the default value.
Context context = getContext();
context.getSystemService(RoleManager.class).clearRoleHoldersAsUser(
RoleManager.ROLE_ASSISTANT, 0, UserHandle.of(userHandle),
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index edf1002..0558648 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -29,6 +29,7 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
+import android.app.ApplicationExitInfo;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.content.BroadcastReceiver;
@@ -38,6 +39,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
@@ -149,6 +151,32 @@
resetHotwordDetectionConnectionLocked();
}
}
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Slog.d(TAG, "onBindingDied to " + name);
+ String packageName = name.getPackageName();
+ ParceledListSlice<ApplicationExitInfo> plistSlice = null;
+ try {
+ plistSlice = mAm.getHistoricalProcessExitReasons(packageName, 0, 1, mUser);
+ } catch (RemoteException e) {
+ // do nothing. The local binder so it can not throw it.
+ }
+ if (plistSlice == null) {
+ return;
+ }
+ List<ApplicationExitInfo> list = plistSlice.getList();
+ if (list.isEmpty()) {
+ return;
+ }
+ // TODO(b/229956310): Refactor the logic of PackageMonitor and onBindingDied
+ ApplicationExitInfo info = list.get(0);
+ if (info.getReason() == ApplicationExitInfo.REASON_USER_REQUESTED
+ && info.getSubReason() == ApplicationExitInfo.SUBREASON_STOP_APP) {
+ // only handle user stopped the application from the task manager
+ mServiceStub.handleUserStop(packageName, mUser);
+ }
+ }
};
VoiceInteractionManagerServiceImpl(Context context, Handler handler,
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 2833489..f65b7c2 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -1500,8 +1500,8 @@
* Get the provisioning status for the IMS RCS capability specified.
*
* If provisioning is not required for the queried
- * {@link ImsRcsManager.RcsImsCapabilityFlag} this method will always return
- * {@code true}.
+ * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS
+ * this method will always return {@code true}.
*
* @see CarrierConfigManager.Ims#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
* @return true if the device is provisioned for the capability or does not require
@@ -1530,8 +1530,8 @@
* Get the provisioning status for the IMS RCS capability specified.
*
* If provisioning is not required for the queried
- * {@link ImsRcsManager.RcsImsCapabilityFlag} this method
- * will always return {@code true}.
+ * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS
+ * this method will always return {@code true}.
*
* <p> Requires Permission:
* <ul>
@@ -1640,7 +1640,8 @@
* </ul>
*
* @return true if provisioning is required for the MMTEL capability and IMS
- * registration technology specified, false if it is not required.
+ * registration technology specified, false if it is not required or if the device does not
+ * support IMS.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public boolean isProvisioningRequiredForCapability(
@@ -1667,7 +1668,8 @@
* </ul>
*
* @return true if provisioning is required for the RCS capability and IMS
- * registration technology specified, false if it is not required.
+ * registration technology specified, false if it is not required or if the device does not
+ * support IMS.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public boolean isRcsProvisioningRequiredForCapability(