diff options
333 files changed, 9239 insertions, 4817 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index 88082f7dfa4b..dd0d07f68547 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -284,9 +284,9 @@ public class EconomyManager { /** @hide */ public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440; /** @hide */ - public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 28800; + public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 4000; /** @hide */ - public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 52000; + public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 28_800; // TODO: add AlarmManager modifier default values /** @hide */ public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0; @@ -373,9 +373,9 @@ public class EconomyManager { /** @hide */ public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000; /** @hide */ - public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 460_000; + public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 100_000; /** @hide */ - public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 900_000; + public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 460_000; // TODO: add JobScheduler modifier default values /** @hide */ public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0; diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java new file mode 100644 index 000000000000..993e178de0ee --- /dev/null +++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java @@ -0,0 +1,407 @@ +/* + * 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.job; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Pools; +import android.util.SparseArray; + +import com.android.server.job.controllers.JobStatus; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; + +/** + * A utility class to maintain a sorted list of currently pending jobs. The sorting system is + * modeled after topological sort, so the returned order may not always be consistent. + */ +class PendingJobQueue { + private final Pools.Pool<AppJobQueue> mAppJobQueuePool = new Pools.SimplePool<>(8); + + /** Set of currently used queues, keyed by source UID. */ + private final SparseArray<AppJobQueue> mCurrentQueues = new SparseArray<>(); + /** + * Same set of AppJobQueues as in {@link #mCurrentQueues}, but ordered by the next timestamp + * to make iterating through the job list faster. + */ + private final PriorityQueue<AppJobQueue> mOrderedQueues = new PriorityQueue<>( + (ajq1, ajq2) -> { + final long t1 = ajq1.peekNextTimestamp(); + final long t2 = ajq2.peekNextTimestamp(); + if (t1 == AppJobQueue.NO_NEXT_TIMESTAMP) { + if (t2 == AppJobQueue.NO_NEXT_TIMESTAMP) { + return 0; + } + return 1; + } else if (t2 == AppJobQueue.NO_NEXT_TIMESTAMP) { + return -1; + } + return Long.compare(t1, t2); + }); + + private int mSize = 0; + + private boolean mNeedToResetIterators = false; + + void add(@NonNull JobStatus job) { + final AppJobQueue ajq = getAppJobQueue(job.getSourceUid(), true); + final long prevTimestamp = ajq.peekNextTimestamp(); + ajq.add(job); + mSize++; + if (prevTimestamp != ajq.peekNextTimestamp()) { + mOrderedQueues.remove(ajq); + mOrderedQueues.offer(ajq); + } + } + + void addAll(@NonNull List<JobStatus> jobs) { + final SparseArray<List<JobStatus>> jobsByUid = new SparseArray<>(); + for (int i = jobs.size() - 1; i >= 0; --i) { + final JobStatus job = jobs.get(i); + List<JobStatus> appJobs = jobsByUid.get(job.getSourceUid()); + if (appJobs == null) { + appJobs = new ArrayList<>(); + jobsByUid.put(job.getSourceUid(), appJobs); + } + appJobs.add(job); + } + for (int i = jobsByUid.size() - 1; i >= 0; --i) { + final AppJobQueue ajq = getAppJobQueue(jobsByUid.keyAt(i), true); + ajq.addAll(jobsByUid.valueAt(i)); + } + mSize += jobs.size(); + mOrderedQueues.clear(); + } + + void clear() { + mSize = 0; + for (int i = mCurrentQueues.size() - 1; i >= 0; --i) { + final AppJobQueue ajq = mCurrentQueues.valueAt(i); + ajq.clear(); + mAppJobQueuePool.release(ajq); + } + mCurrentQueues.clear(); + mOrderedQueues.clear(); + } + + boolean contains(@NonNull JobStatus job) { + final AppJobQueue ajq = mCurrentQueues.get(job.getSourceUid()); + if (ajq == null) { + return false; + } + return ajq.contains(job); + } + + private AppJobQueue getAppJobQueue(int uid, boolean create) { + AppJobQueue ajq = mCurrentQueues.get(uid); + if (ajq == null && create) { + ajq = mAppJobQueuePool.acquire(); + if (ajq == null) { + ajq = new AppJobQueue(); + } + mCurrentQueues.put(uid, ajq); + } + return ajq; + } + + @Nullable + JobStatus next() { + if (mNeedToResetIterators) { + mOrderedQueues.clear(); + for (int i = mCurrentQueues.size() - 1; i >= 0; --i) { + final AppJobQueue ajq = mCurrentQueues.valueAt(i); + ajq.resetIterator(0); + mOrderedQueues.offer(ajq); + } + mNeedToResetIterators = false; + } else if (mOrderedQueues.size() == 0) { + for (int i = mCurrentQueues.size() - 1; i >= 0; --i) { + final AppJobQueue ajq = mCurrentQueues.valueAt(i); + mOrderedQueues.offer(ajq); + } + } + final AppJobQueue earliestQueue = mOrderedQueues.poll(); + if (earliestQueue != null) { + JobStatus job = earliestQueue.next(); + mOrderedQueues.offer(earliestQueue); + return job; + } + return null; + } + + boolean remove(@NonNull JobStatus job) { + final AppJobQueue ajq = getAppJobQueue(job.getSourceUid(), false); + if (ajq == null) { + return false; + } + + final long prevTimestamp = ajq.peekNextTimestamp(); + if (!ajq.remove(job)) { + return false; + } + + mSize--; + if (ajq.size() == 0) { + mCurrentQueues.remove(job.getSourceUid()); + mOrderedQueues.remove(ajq); + ajq.clear(); + mAppJobQueuePool.release(ajq); + } else if (prevTimestamp != ajq.peekNextTimestamp()) { + mOrderedQueues.remove(ajq); + mOrderedQueues.offer(ajq); + } + + return true; + } + + /** Resets the iterating index to the front of the queue. */ + void resetIterator() { + // Lazily reset the iterating indices (avoid looping through all the current queues until + // absolutely necessary). + mNeedToResetIterators = true; + } + + int size() { + return mSize; + } + + private static final class AppJobQueue { + static final long NO_NEXT_TIMESTAMP = -1L; + + private static class AdjustedJobStatus { + public long adjustedEnqueueTime; + public JobStatus job; + + void clear() { + adjustedEnqueueTime = 0; + job = null; + } + } + + private static final Comparator<AdjustedJobStatus> sJobComparator = (aj1, aj2) -> { + if (aj1 == aj2) { + return 0; + } + final JobStatus job1 = aj1.job; + final JobStatus job2 = aj2.job; + // Jobs with an override state set (via adb) should be put first as tests/developers + // expect the jobs to run immediately. + if (job1.overrideState != job2.overrideState) { + // Higher override state (OVERRIDE_FULL) should be before lower state + // (OVERRIDE_SOFT) + return job2.overrideState - job1.overrideState; + } + + final boolean job1EJ = job1.isRequestedExpeditedJob(); + final boolean job2EJ = job2.isRequestedExpeditedJob(); + if (job1EJ != job2EJ) { + // Attempt to run requested expedited jobs ahead of regular jobs, regardless of + // expedited job quota. + return job1EJ ? -1 : 1; + } + + final int job1Priority = job1.getEffectivePriority(); + final int job2Priority = job2.getEffectivePriority(); + if (job1Priority != job2Priority) { + // Use the priority set by an app for intra-app job ordering. Higher + // priority should be before lower priority. + return job2Priority - job1Priority; + } + + if (job1.lastEvaluatedBias != job2.lastEvaluatedBias) { + // Higher bias should go first. + return job2.lastEvaluatedBias - job1.lastEvaluatedBias; + } + + if (job1.enqueueTime < job2.enqueueTime) { + return -1; + } + return job1.enqueueTime > job2.enqueueTime ? 1 : 0; + }; + + private static final Pools.Pool<AdjustedJobStatus> mAdjustedJobStatusPool = + new Pools.SimplePool<>(16); + + private final List<AdjustedJobStatus> mJobs = new ArrayList<>(); + private int mCurIndex = 0; + + void add(@NonNull JobStatus jobStatus) { + AdjustedJobStatus adjustedJobStatus = mAdjustedJobStatusPool.acquire(); + if (adjustedJobStatus == null) { + adjustedJobStatus = new AdjustedJobStatus(); + } + adjustedJobStatus.adjustedEnqueueTime = jobStatus.enqueueTime; + adjustedJobStatus.job = jobStatus; + + int where = Collections.binarySearch(mJobs, adjustedJobStatus, sJobComparator); + if (where < 0) { + where = ~where; + } + mJobs.add(where, adjustedJobStatus); + if (where < mCurIndex) { + // Shift the current index back to make sure the new job is evaluated on the next + // iteration. + mCurIndex = where; + } + + if (where > 0) { + final long prevTimestamp = mJobs.get(where - 1).adjustedEnqueueTime; + adjustedJobStatus.adjustedEnqueueTime = + Math.max(prevTimestamp, adjustedJobStatus.adjustedEnqueueTime); + } + final int numJobs = mJobs.size(); + if (where < numJobs - 1) { + // Potentially need to adjust following job timestamps as well. + for (int i = where; i < numJobs; ++i) { + final AdjustedJobStatus ajs = mJobs.get(i); + if (adjustedJobStatus.adjustedEnqueueTime < ajs.adjustedEnqueueTime) { + // No further need to adjust. + break; + } + ajs.adjustedEnqueueTime = adjustedJobStatus.adjustedEnqueueTime; + } + } + } + + void addAll(@NonNull List<JobStatus> jobs) { + int earliestIndex = Integer.MAX_VALUE; + + for (int i = jobs.size() - 1; i >= 0; --i) { + final JobStatus job = jobs.get(i); + + AdjustedJobStatus adjustedJobStatus = mAdjustedJobStatusPool.acquire(); + if (adjustedJobStatus == null) { + adjustedJobStatus = new AdjustedJobStatus(); + } + adjustedJobStatus.adjustedEnqueueTime = job.enqueueTime; + adjustedJobStatus.job = job; + + int where = Collections.binarySearch(mJobs, adjustedJobStatus, sJobComparator); + if (where < 0) { + where = ~where; + } + mJobs.add(where, adjustedJobStatus); + if (where < mCurIndex) { + // Shift the current index back to make sure the new job is evaluated on the + // next iteration. + mCurIndex = where; + } + earliestIndex = Math.min(earliestIndex, where); + } + + final int numJobs = mJobs.size(); + for (int i = Math.max(earliestIndex, 1); i < numJobs; ++i) { + final AdjustedJobStatus ajs = mJobs.get(i); + final AdjustedJobStatus prev = mJobs.get(i - 1); + ajs.adjustedEnqueueTime = + Math.max(ajs.adjustedEnqueueTime, prev.adjustedEnqueueTime); + } + } + + void clear() { + mJobs.clear(); + mCurIndex = 0; + } + + boolean contains(@NonNull JobStatus job) { + return indexOf(job) >= 0; + } + + private int indexOf(@NonNull JobStatus jobStatus) { + AdjustedJobStatus adjustedJobStatus = mAdjustedJobStatusPool.acquire(); + if (adjustedJobStatus == null) { + adjustedJobStatus = new AdjustedJobStatus(); + } + adjustedJobStatus.adjustedEnqueueTime = jobStatus.enqueueTime; + adjustedJobStatus.job = jobStatus; + + int where = Collections.binarySearch(mJobs, adjustedJobStatus, sJobComparator); + adjustedJobStatus.clear(); + mAdjustedJobStatusPool.release(adjustedJobStatus); + return where; + } + + @Nullable + JobStatus next() { + if (mCurIndex >= mJobs.size()) { + return null; + } + JobStatus next = mJobs.get(mCurIndex).job; + mCurIndex++; + return next; + } + + long peekNextTimestamp() { + if (mCurIndex >= mJobs.size()) { + return NO_NEXT_TIMESTAMP; + } + return mJobs.get(mCurIndex).adjustedEnqueueTime; + } + + boolean remove(@NonNull JobStatus jobStatus) { + final int idx = indexOf(jobStatus); + if (idx < 0) { + // Doesn't exist... + return false; + } + final AdjustedJobStatus adjustedJobStatus = mJobs.remove(idx); + adjustedJobStatus.clear(); + mAdjustedJobStatusPool.release(adjustedJobStatus); + if (idx < mCurIndex) { + mCurIndex--; + } + return true; + } + + /** + * Resets the internal index to point to the first JobStatus whose adjusted time is equal to + * or after the given timestamp. + */ + void resetIterator(long earliestEnqueueTime) { + if (earliestEnqueueTime == 0 || mJobs.size() == 0) { + mCurIndex = 0; + return; + } + + // Binary search + int low = 0; + int high = mJobs.size() - 1; + + while (low < high) { + int mid = (low + high) >>> 1; + AdjustedJobStatus midVal = mJobs.get(mid); + + if (midVal.adjustedEnqueueTime < earliestEnqueueTime) { + low = mid + 1; + } else if (midVal.adjustedEnqueueTime > earliestEnqueueTime) { + high = mid - 1; + } else { + high = mid; + } + } + mCurIndex = high; + } + + int size() { + return mJobs.size(); + } + } +} diff --git a/core/api/current.txt b/core/api/current.txt index 37022d0ab402..5022f4ab11db 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1755,7 +1755,7 @@ package android { field public static final int windowShowWallpaper = 16843410; // 0x1010292 field public static final int windowSoftInputMode = 16843307; // 0x101022b field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d - field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e + field @Deprecated public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e field public static final int windowSplashScreenBackground = 16844332; // 0x101062c field public static final int windowSplashScreenBehavior; field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f @@ -44820,7 +44820,7 @@ package android.text { public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback { ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); - ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); + ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public void ellipsized(int, int); method public int getBottomPadding(); method public int getEllipsisCount(int); @@ -44838,9 +44838,9 @@ package android.text { method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); - method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); + method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); - method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); + method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); } @@ -52262,7 +52262,7 @@ package android.view.animation { method protected android.view.animation.Animation clone() throws java.lang.CloneNotSupportedException; method public long computeDurationHint(); method protected void ensureInterpolator(); - method @Deprecated @ColorInt public int getBackgroundColor(); + method @ColorInt public int getBackgroundColor(); method @Deprecated public boolean getDetachWallpaper(); method public long getDuration(); method public boolean getFillAfter(); @@ -52287,7 +52287,7 @@ package android.view.animation { method public void restrictDuration(long); method public void scaleCurrentDuration(float); method public void setAnimationListener(android.view.animation.Animation.AnimationListener); - method @Deprecated public void setBackgroundColor(@ColorInt int); + method public void setBackgroundColor(@ColorInt int); method @Deprecated public void setDetachWallpaper(boolean); method public void setDuration(long); method public void setFillAfter(boolean); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0a748142d65e..dd4d4dcd3d03 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -403,6 +403,7 @@ package android { public static final class R.drawable { field public static final int ic_info = 17301684; // 0x10800b4 + field public static final int ic_safety_protection; } public static final class R.raw { @@ -438,6 +439,7 @@ package android { field public static final int config_systemContacts = 17039403; // 0x104002b field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035 + field public static final int config_systemSettingsIntelligence; field public static final int config_systemShell = 17039402; // 0x104002a field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e field public static final int config_systemSupervision; @@ -448,6 +450,7 @@ package android { field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037 field public static final int config_systemWellbeing = 17039408; // 0x1040030 field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f + field public static final int safety_protection_display_text; } public static final class R.style { @@ -9802,8 +9805,8 @@ package android.os { method public boolean isRestrictedProfile(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull String); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean); @@ -10082,10 +10085,10 @@ package android.permission { public final class PermissionManager { method public int checkDeviceIdentifierAccess(@Nullable String, @Nullable String, @Nullable String, int, int); - method public int checkPermissionForDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String); - method public int checkPermissionForDataDeliveryFromDataSource(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String); + method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String); + method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForDataDeliveryFromDataSource(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String); method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource); - method public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String); + method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String); method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource); method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages(); method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages(); @@ -11802,11 +11805,23 @@ package android.service.translation { package android.service.trust { + public final class GrantTrustResult implements android.os.Parcelable { + method public int describeContents(); + method public int getStatus(); + method @NonNull public static String statusToString(int); + method @NonNull public static android.service.trust.GrantTrustResult withStatus(int); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.trust.GrantTrustResult> CREATOR; + field public static final int STATUS_UNKNOWN = 0; // 0x0 + field public static final int STATUS_UNLOCKED_BY_GRANT = 1; // 0x1 + } + public class TrustAgentService extends android.app.Service { ctor public TrustAgentService(); method public final void addEscrowToken(byte[], android.os.UserHandle); method @Deprecated public final void grantTrust(CharSequence, long, boolean); - method public final void grantTrust(CharSequence, long, int); + method @Deprecated public final void grantTrust(CharSequence, long, int); + method public final void grantTrust(@NonNull CharSequence, long, int, @Nullable java.util.function.Consumer<android.service.trust.GrantTrustResult>); method public final void isEscrowTokenActive(long, android.os.UserHandle); method public final void lockUser(); method public final android.os.IBinder onBind(android.content.Intent); @@ -11819,7 +11834,8 @@ package android.service.trust { method public void onEscrowTokenStateReceived(long, int); method public void onTrustTimeout(); method public void onUnlockAttempt(boolean); - method public void onUserRequestedUnlock(); + method public void onUserMayRequestUnlock(); + method public void onUserRequestedUnlock(boolean); method public final void removeEscrowToken(long, android.os.UserHandle); method public final void revokeTrust(); method public final void setManagingTrust(boolean); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e8c903fc2f13..5b05b45d05db 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4475,23 +4475,6 @@ public class ActivityManager { } /** - * Logs out current current foreground user by switching to the system user and stopping the - * user being switched from. - * @hide - */ - public static void logoutCurrentUser() { - int currentUser = ActivityManager.getCurrentUser(); - if (currentUser != UserHandle.USER_SYSTEM) { - try { - getService().switchUser(UserHandle.USER_SYSTEM); - getService().stopUser(currentUser, /* force= */ false, null); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - } - - /** * Stops the given {@code userId}. * * @hide diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 0178fa143445..0d8103675cc2 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1162,6 +1162,7 @@ public class ActivityOptions extends ComponentOptions { case ANIM_CUSTOM: mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0); mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0); + mCustomBackgroundColor = opts.getInt(KEY_ANIM_BACKGROUND_COLOR, 0); mAnimationStartedListener = IRemoteCallback.Stub.asInterface( opts.getBinder(KEY_ANIM_START_LISTENER)); break; diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java index b170aa2a1325..b90408d4395e 100644 --- a/core/java/android/app/admin/SecurityLog.java +++ b/core/java/android/app/admin/SecurityLog.java @@ -517,14 +517,15 @@ public class SecurityLog { public static final int TAG_PASSWORD_CHANGED = SecurityLogTags.SECURITY_PASSWORD_CHANGED; /** - * Indicates that the device attempts to connect to a WiFi network. - * The log entry contains the following information about the + * Indicates that an event occurred as the device attempted to connect to + * a WiFi network. The log entry contains the following information about the * event, encapsulated in an {@link Object} array and accessible via * {@link SecurityEvent#getData()}: - * <li> [0] The SSID of the network ({@code String}) - * <li> [1] The BSSID of the network ({@code String}) - * <li> [2] Whether the connection is successful ({@code Integer}, 1 if successful, 0 otherwise) - * <li> [3] Optional human-readable failure reason, empty string if none ({@code String}) + * <li> [0] Last 2 octets of the network BSSID ({@code String}, in the form "xx:xx:xx:xx:AA:BB") + * <li> [1] Type of event that occurred ({@code String}). Event types are CONNECTED, + * DISCONNECTED, ASSOCIATING, ASSOCIATED, EAP_METHOD_SELECTED, EAP_FAILURE, + * SSID_TEMP_DISABLED, and OPEN_SSL_FAILURE. + * <li> [2] Optional human-readable failure reason, empty string if none ({@code String}) */ public static final int TAG_WIFI_CONNECTION = SecurityLogTags.SECURITY_WIFI_CONNECTION; @@ -533,9 +534,8 @@ public class SecurityLog { * The log entry contains the following information about the * event, encapsulated in an {@link Object} array and accessible via * {@link SecurityEvent#getData()}: - * <li> [0] The SSID of the connected network ({@code String}) - * <li> [1] The BSSID of the connected network ({@code String}) - * <li> [2] Optional human-readable disconnection reason, empty string if none ({@code String}) + * <li> [0] Last 2 octets of the network BSSID ({@code String}, in the form "xx:xx:xx:xx:AA:BB") + * <li> [1] Optional human-readable disconnection reason, empty string if none ({@code String}) */ public static final int TAG_WIFI_DISCONNECTION = SecurityLogTags.SECURITY_WIFI_DISCONNECTION; diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags index 5f41109600b2..b06e5a5849fd 100644 --- a/core/java/android/app/admin/SecurityLogTags.logtags +++ b/core/java/android/app/admin/SecurityLogTags.logtags @@ -41,7 +41,7 @@ option java_package android.app.admin 210034 security_camera_policy_set (package|3),(admin_user|1),(target_user|1),(disabled|1) 210035 security_password_complexity_required (package|3),(admin_user|1),(target_user|1),(complexity|1) 210036 security_password_changed (password_complexity|1),(target_user|1) -210037 security_wifi_connection (ssid|3),(bssid|3),(success|1),(reason|3) -210038 security_wifi_disconnection (ssid|3),(bssid|3),(reason|3) +210037 security_wifi_connection (bssid|3),(event_type|3),(reason|3) +210038 security_wifi_disconnection (bssid|3),(reason|3) 210039 security_bluetooth_connection (addr|3),(success|1),(reason|3) 210040 security_bluetooth_disconnection (addr|3),(reason|3)
\ No newline at end of file diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index b786444faa8c..45146fd95104 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -26,7 +26,8 @@ import android.hardware.biometrics.BiometricSourceType; */ interface ITrustManager { void reportUnlockAttempt(boolean successful, int userId); - void reportUserRequestedUnlock(int userId); + void reportUserRequestedUnlock(int userId, boolean dismissKeyguard); + void reportUserMayRequestUnlock(int userId); void reportUnlockLockout(int timeoutMs, int userId); void reportEnabledTrustAgentsChanged(int userId); void registerTrustListener(in ITrustListener trustListener); diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 70b7de0767e4..9e825b7207e0 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -92,10 +92,25 @@ public class TrustManager { * Reports that the user {@code userId} is likely interested in unlocking the device. * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + * + * @param dismissKeyguard whether the user wants to dismiss keyguard + */ + public void reportUserRequestedUnlock(int userId, boolean dismissKeyguard) { + try { + mService.reportUserRequestedUnlock(userId, dismissKeyguard); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Reports that the user {@code userId} may want to unlock the device soon. + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. */ - public void reportUserRequestedUnlock(int userId) { + public void reportUserMayRequestUnlock(int userId) { try { - mService.reportUserRequestedUnlock(userId); + mService.reportUserMayRequestUnlock(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 15685000d6ba..56939f0ae650 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -76,6 +76,56 @@ public final class CompanionDeviceManager { private static final String LOG_TAG = "CompanionDeviceManager"; /** + * The result code to propagate back to the originating activity, indicates the association + * dialog is explicitly declined by the users. + * + * @hide + */ + public static final int RESULT_USER_REJECTED = 1; + + /** + * The result code to propagate back to the originating activity, indicates the association + * dialog is dismissed if there's no device found after 20 seconds. + * + * @hide + */ + public static final int RESULT_DISCOVERY_TIMEOUT = 2; + + /** + * The result code to propagate back to the originating activity, indicates the internal error + * in CompanionDeviceManager. + * + * @hide + */ + public static final int RESULT_INTERNAL_ERROR = 3; + + /** + * Requesting applications will receive the String in {@link Callback#onFailure} if the + * association dialog is explicitly declined by the users. e.g. press the Don't allow button. + * + * @hide + */ + public static final String REASON_USER_REJECTED = "user_rejected"; + + /** + * Requesting applications will receive the String in {@link Callback#onFailure} if there's + * no device found after 20 seconds. + * + * @hide + */ + public static final String REASON_DISCOVERY_TIMEOUT = "discovery_timeout"; + + /** + * Requesting applications will receive the String in {@link Callback#onFailure} if the + * association dialog is in-explicitly declined by the users. e.g. phone is locked, switch to + * another app or press outside the dialog. + * + * @hide + */ + public static final String REASON_CANCELED = "canceled"; + + + /** * A device, returned in the activity result of the {@link IntentSender} received in * {@link Callback#onDeviceFound} * diff --git a/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java index df13ade2bf5e..bd25b8f2ad88 100644 --- a/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java +++ b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java @@ -16,9 +16,9 @@ package android.hardware.location; +import android.os.BadParcelableException; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; /** * Geofence Hardware Request used for internal location services communication. @@ -139,11 +139,8 @@ public final class GeofenceHardwareRequestParcelable implements Parcelable { @Override public GeofenceHardwareRequestParcelable createFromParcel(Parcel parcel) { int geofenceType = parcel.readInt(); - if(geofenceType != GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) { - Log.e( - "GeofenceHardwareRequest", - String.format("Invalid Geofence type: %d", geofenceType)); - return null; + if (geofenceType != GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) { + throw new BadParcelableException("Invalid Geofence type: " + geofenceType); } GeofenceHardwareRequest request = GeofenceHardwareRequest.createCircularGeofence( diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 02302a20fe38..f9ed0e3db499 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -81,6 +81,7 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_CAN_START_STYLUS_HANDWRITING = 100; private static final int DO_START_STYLUS_HANDWRITING = 110; private static final int DO_INIT_INK_WINDOW = 120; + private static final int DO_FINISH_STYLUS_HANDWRITING = 130; final WeakReference<InputMethodServiceInternal> mTarget; final Context mContext; @@ -263,6 +264,10 @@ class IInputMethodWrapper extends IInputMethod.Stub inputMethod.initInkWindow(); return; } + case DO_FINISH_STYLUS_HANDWRITING: { + inputMethod.finishStylusHandwriting(); + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); @@ -427,4 +432,10 @@ class IInputMethodWrapper extends IInputMethod.Stub public void initInkWindow() { mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_INIT_INK_WINDOW)); } + + @BinderThread + @Override + public void finishStylusHandwriting() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_STYLUS_HANDWRITING)); + } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index b46bb3257c86..4fdd53425328 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -994,6 +994,15 @@ public class InputMethodService extends AbstractInputMethodService { /** * {@inheritDoc} + * @hide + */ + @Override + public void finishStylusHandwriting() { + InputMethodService.this.finishStylusHandwriting(); + } + + /** + * {@inheritDoc} */ @MainThread @Override @@ -2461,7 +2470,7 @@ public class InputMethodService extends AbstractInputMethodService { mHandwritingEventReceiver = null; mInkWindow.hide(false /* remove */); - mPrivOps.finishStylusHandwriting(requestId); + mPrivOps.resetStylusHandwriting(requestId); mOnPreparedStylusHwCalled = false; onFinishStylusHandwriting(); } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 1c85f692b232..42e6ac4df8af 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -406,7 +406,7 @@ public class Build { public static final String CODENAME = getString("ro.build.version.codename"); /** - * All known codenames starting from {@link VERSION_CODES.Q}. + * All known codenames that are present in {@link VERSION_CODES}. * * <p>This includes in development codenames as well, i.e. if {@link #CODENAME} is not "REL" * then the value of that is present in this set. diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 043e688489a4..a4f6004a288a 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -107,9 +107,6 @@ public final class Trace { public static final long TRACE_TAG_RRO = 1L << 26; /** @hide */ public static final long TRACE_TAG_THERMAL = 1L << 27; - /** @hide */ - - public static final long TRACE_TAG_APEX_MANAGER = 1L << 18; private static final long TRACE_TAG_NOT_READY = 1L << 63; private static final int MAX_SECTION_NAME_LEN = 127; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 608c424451b7..c4cb3195e485 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2186,9 +2186,17 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, - Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) - @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS, + android.Manifest.permission.QUERY_USERS, + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) + @UserHandleAware( + enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU, + requiresAnyOfPermissionsIfNotCaller = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS, + android.Manifest.permission.QUERY_USERS}) public boolean isUserNameSet() { try { return mService.isUserNameSet(getContextUserIfAppropriate()); @@ -2292,8 +2300,11 @@ public class UserManager { * @hide */ @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS, + android.Manifest.permission.QUERY_USERS}) @UserHandleAware - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull String userType) { try { return mService.isUserOfType(mUserId, userType); diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 642c618ba1f7..5bed32cb0438 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -167,11 +167,27 @@ public final class VibrationAttributes implements Parcelable { public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 0x2; /** + * Flag requesting vibration effect to be played with fresh user settings values. + * + * <p>This flag is not protected by any permission, but vibrations that use it require an extra + * query of user vibration intensity settings, ringer mode and other controls that affect the + * vibration effect playback, which can increase the latency for the overall request. + * + * <p>This is intended to be used on scenarios where the user settings might have changed + * recently, and needs to be applied to this vibration, like settings controllers that preview + * newly set intensities to the user. + * + * @hide + */ + public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 0x3; + + /** * All flags supported by vibrator service, update it when adding new flag. * @hide */ public static final int FLAG_ALL_SUPPORTED = - FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF + | FLAG_INVALIDATE_SETTINGS_CACHE; /** Creates a new {@link VibrationAttributes} instance with given usage. */ public static @NonNull VibrationAttributes createForUsage(@Usage int usage) { @@ -446,8 +462,10 @@ public final class VibrationAttributes implements Parcelable { } /** - * Set flags - * @param flags combination of flags to be set. + * Sets only the flags specified in the bitmask, leaving the other supported flag values + * unchanged in the builder. + * + * @param flags Combination of flags to be set. * @param mask Bit range that should be changed. * @return the same Builder instance. */ @@ -456,5 +474,18 @@ public final class VibrationAttributes implements Parcelable { mFlags = (mFlags & ~mask) | (flags & mask); return this; } + + /** + * Set all supported flags with given combination of flags, overriding any previous values + * set to this builder. + * + * @param flags combination of flags to be set. + * @return the same Builder instance. + * + * @hide + */ + public @NonNull Builder setFlags(@Flag int flags) { + return setFlags(flags, FLAG_ALL_SUPPORTED); + } } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 9971cbcfa586..646a7095c1b3 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -301,28 +301,7 @@ public class StorageManager { /** @hide The volume is not encrypted. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int ENCRYPTION_STATE_NONE = - IVold.ENCRYPTION_STATE_NONE; - - /** @hide The volume has been encrypted succesfully. */ - public static final int ENCRYPTION_STATE_OK = - IVold.ENCRYPTION_STATE_OK; - - /** @hide The volume is in a bad state. */ - public static final int ENCRYPTION_STATE_ERROR_UNKNOWN = - IVold.ENCRYPTION_STATE_ERROR_UNKNOWN; - - /** @hide Encryption is incomplete */ - public static final int ENCRYPTION_STATE_ERROR_INCOMPLETE = - IVold.ENCRYPTION_STATE_ERROR_INCOMPLETE; - - /** @hide Encryption is incomplete and irrecoverable */ - public static final int ENCRYPTION_STATE_ERROR_INCONSISTENT = - IVold.ENCRYPTION_STATE_ERROR_INCONSISTENT; - - /** @hide Underlying data is corrupt */ - public static final int ENCRYPTION_STATE_ERROR_CORRUPT = - IVold.ENCRYPTION_STATE_ERROR_CORRUPT; + public static final int ENCRYPTION_STATE_NONE = 1; private static volatile IStorageManager sStorageManager = null; @@ -3033,15 +3012,10 @@ public class StorageManager { @GuardedBy("mFuseAppLoopLock") private @Nullable FuseAppLoop mFuseAppLoop = null; - /// Consts to match the password types in cryptfs.h /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int CRYPT_TYPE_PASSWORD = IVold.PASSWORD_TYPE_PASSWORD; + public static final int CRYPT_TYPE_PASSWORD = 0; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int CRYPT_TYPE_DEFAULT = IVold.PASSWORD_TYPE_DEFAULT; - /** @hide */ - public static final int CRYPT_TYPE_PATTERN = IVold.PASSWORD_TYPE_PATTERN; - /** @hide */ - public static final int CRYPT_TYPE_PIN = IVold.PASSWORD_TYPE_PIN; + public static final int CRYPT_TYPE_DEFAULT = 1; } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 7a797ce28870..69728756d745 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -250,6 +250,10 @@ public final class PermissionManager { * will evaluate the permission access based on the current fg/bg state of the app and * leave a record that the data was accessed. * + * <p>Requires the start of the AttributionSource chain to have the UPDATE_APP_OPS_STATS + * permission for the app op accesses to be given the TRUSTED_PROXY/PROXIED flags, otherwise the + * accesses will have the UNTRUSTED flags. + * * @param permission The permission to check. * @param attributionSource the permission identity * @param message A message describing the reason the permission was checked @@ -259,6 +263,7 @@ public final class PermissionManager { * @see #checkPermissionForPreflight(String, AttributionSource) */ @PermissionCheckerManager.PermissionResult + @RequiresPermission(value = Manifest.permission.UPDATE_APP_OPS_STATS, conditional = true) public int checkPermissionForDataDelivery(@NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message) { return PermissionChecker.checkPermissionForDataDelivery(mContext, permission, @@ -278,9 +283,14 @@ public final class PermissionManager { * @return The permission check result which is either {@link #PERMISSION_GRANTED} * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * + * <p>Requires the start of the AttributionSource chain to have the UPDATE_APP_OPS_STATS + * permission for the app op accesses to be given the TRUSTED_PROXY/PROXIED flags, otherwise the + * accesses will have the UNTRUSTED flags. + * * @see #checkPermissionForDataDelivery(String, AttributionSource, String) */ @PermissionCheckerManager.PermissionResult + @RequiresPermission(value = Manifest.permission.UPDATE_APP_OPS_STATS, conditional = true) public int checkPermissionForStartDataDelivery(@NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message) { return PermissionChecker.checkPermissionForDataDelivery(mContext, permission, @@ -320,6 +330,10 @@ public final class PermissionManager { * will evaluate the permission access based on the current fg/bg state of the app and * leave a record that the data was accessed. * + * <p>Requires the start of the AttributionSource chain to have the UPDATE_APP_OPS_STATS + * permission for the app op accesses to be given the TRUSTED_PROXY/PROXIED flags, otherwise the + * accesses will have the UNTRUSTED flags. + * * @param permission The permission to check. * @param attributionSource the permission identity * @param message A message describing the reason the permission was checked @@ -329,6 +343,7 @@ public final class PermissionManager { * @see #checkPermissionForPreflight(String, AttributionSource) */ @PermissionCheckerManager.PermissionResult + @RequiresPermission(value = Manifest.permission.UPDATE_APP_OPS_STATS, conditional = true) public int checkPermissionForDataDeliveryFromDataSource(@NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message) { return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(mContext, permission, diff --git a/core/java/android/service/trust/GrantTrustResult.aidl b/core/java/android/service/trust/GrantTrustResult.aidl new file mode 100644 index 000000000000..d24a6bc62f17 --- /dev/null +++ b/core/java/android/service/trust/GrantTrustResult.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.trust; + +parcelable GrantTrustResult; diff --git a/core/java/android/service/trust/GrantTrustResult.java b/core/java/android/service/trust/GrantTrustResult.java new file mode 100644 index 000000000000..7cf708a3d6e3 --- /dev/null +++ b/core/java/android/service/trust/GrantTrustResult.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.trust; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result type for a callback in a call to + * {@link TrustAgentService#grantTrust(CharSequence, long, int)}. + * + * @hide + */ +@DataClass(genHiddenConstructor = true) +@SystemApi +public final class GrantTrustResult implements Parcelable { + + /** Result status is unknown to this version of the SDK. */ + public static final int STATUS_UNKNOWN = 0; + + /** The device went from locked to unlocked as a result of the call. */ + public static final int STATUS_UNLOCKED_BY_GRANT = 1; + + @Status + private int mStatus; + + /** Returns a new {@link GrantTrustResult} with the specified status. */ + @NonNull + public static GrantTrustResult withStatus(@Status int status) { + return new GrantTrustResult(status); + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/trust/GrantTrustResult.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @IntDef(prefix = "STATUS_", value = { + STATUS_UNKNOWN, + STATUS_UNLOCKED_BY_GRANT + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Status {} + + @NonNull + @DataClass.Generated.Member + public static String statusToString(@Status int value) { + switch (value) { + case STATUS_UNKNOWN: + return "STATUS_UNKNOWN"; + case STATUS_UNLOCKED_BY_GRANT: + return "STATUS_UNLOCKED_BY_GRANT"; + default: return Integer.toHexString(value); + } + } + + /** + * Creates a new GrantTrustResult. + * + * @hide + */ + @DataClass.Generated.Member + public GrantTrustResult( + @Status int status) { + this.mStatus = status; + + if (!(mStatus == STATUS_UNKNOWN) + && !(mStatus == STATUS_UNLOCKED_BY_GRANT)) { + throw new java.lang.IllegalArgumentException( + "status was " + mStatus + " but must be one of: " + + "STATUS_UNKNOWN(" + STATUS_UNKNOWN + "), " + + "STATUS_UNLOCKED_BY_GRANT(" + STATUS_UNLOCKED_BY_GRANT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @Status int getStatus() { + return mStatus; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mStatus); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ GrantTrustResult(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int status = in.readInt(); + + this.mStatus = status; + + if (!(mStatus == STATUS_UNKNOWN) + && !(mStatus == STATUS_UNLOCKED_BY_GRANT)) { + throw new java.lang.IllegalArgumentException( + "status was " + mStatus + " but must be one of: " + + "STATUS_UNKNOWN(" + STATUS_UNKNOWN + "), " + + "STATUS_UNLOCKED_BY_GRANT(" + STATUS_UNLOCKED_BY_GRANT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<GrantTrustResult> CREATOR + = new Parcelable.Creator<GrantTrustResult>() { + @Override + public GrantTrustResult[] newArray(int size) { + return new GrantTrustResult[size]; + } + + @Override + public GrantTrustResult createFromParcel(@NonNull android.os.Parcel in) { + return new GrantTrustResult(in); + } + }; + + @DataClass.Generated( + time = 1647878197834L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/trust/GrantTrustResult.java", + inputSignatures = "public static final int STATUS_UNKNOWN\npublic static final int STATUS_UNLOCKED_BY_GRANT\nprivate @android.service.trust.GrantTrustResult.Status int mStatus\npublic static @android.annotation.NonNull android.service.trust.GrantTrustResult withStatus(int)\nclass GrantTrustResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl index ec3b8575ed36..70c29a682cd1 100644 --- a/core/java/android/service/trust/ITrustAgentService.aidl +++ b/core/java/android/service/trust/ITrustAgentService.aidl @@ -25,7 +25,8 @@ import android.service.trust.ITrustAgentServiceCallback; */ interface ITrustAgentService { oneway void onUnlockAttempt(boolean successful); - oneway void onUserRequestedUnlock(); + oneway void onUserRequestedUnlock(boolean dismissKeyguard); + oneway void onUserMayRequestUnlock(); oneway void onUnlockLockout(int timeoutMs); oneway void onTrustTimeout(); oneway void onDeviceLocked(); diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl index 6b11e7463abc..e9e40c0175ac 100644 --- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl +++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl @@ -18,13 +18,15 @@ package android.service.trust; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; +import com.android.internal.infra.AndroidFuture; /** * Communication channel from the TrustAgentService back to TrustManagerService. * @hide */ oneway interface ITrustAgentServiceCallback { - void grantTrust(CharSequence message, long durationMs, int flags); + void grantTrust( + CharSequence message, long durationMs, int flags, in AndroidFuture resultCallback); void revokeTrust(); void lockUser(); void setManagingTrust(boolean managingTrust); diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index 8f6e1e00c3f4..559313a30dfa 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -19,6 +19,7 @@ package android.service.trust; import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; @@ -39,9 +40,12 @@ import android.os.UserManager; import android.util.Log; import android.util.Slog; +import com.android.internal.infra.AndroidFuture; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.function.Consumer; /** * A service that notifies the system about whether it believes the environment of the device @@ -183,6 +187,7 @@ public class TrustAgentService extends Service { private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8; private static final int MSG_ESCROW_TOKEN_REMOVED = 9; private static final int MSG_USER_REQUESTED_UNLOCK = 10; + private static final int MSG_USER_MAY_REQUEST_UNLOCK = 11; private static final String EXTRA_TOKEN = "token"; private static final String EXTRA_TOKEN_HANDLE = "token_handle"; @@ -217,7 +222,10 @@ public class TrustAgentService extends Service { onUnlockAttempt(msg.arg1 != 0); break; case MSG_USER_REQUESTED_UNLOCK: - onUserRequestedUnlock(); + onUserRequestedUnlock(msg.arg1 != 0); + break; + case MSG_USER_MAY_REQUEST_UNLOCK: + onUserMayRequestUnlock(); break; case MSG_UNLOCK_LOCKOUT: onDeviceUnlockLockout(msg.arg1); @@ -297,16 +305,37 @@ public class TrustAgentService extends Service { } /** + * Called when the user has interacted with the locked device such that they are likely to want + * it to be unlocked soon. This approximates the timing when, for example, the platform would + * check for face authentication to unlock the device. + * + * This signal can be used for the agent to make preparations to quickly unlock the device + * with {@link #onUserRequestedUnlock}. Agents should not unlock the device based solely on this + * signal. There is no guarantee that this method will be called before + * {@link #onUserRequestedUnlock(boolean)}. + */ + public void onUserMayRequestUnlock() { + } + + /** * Called when the user has interacted with the locked device such that they likely want it - * to be unlocked. This approximates the timing when, for example, the platform would check for - * face authentication to unlock the device. + * to be unlocked. + * + * When this is called, there is a high probability that the user wants to unlock the device and + * that a biometric method is either not available or not the optimal method at this time. For + * example, this may be called after some kinds of biometric authentication failure. + * + * A call to this method may be preceded by a call to {@link #onUserMayRequestUnlock} which + * the agent can use as a signal to prepare for a subsequent call to this method. * * To attempt to unlock the device, the agent needs to call * {@link #grantTrust(CharSequence, long, int)}. * + * @param dismissKeyguard true when the user wants keyguard dismissed + * * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE */ - public void onUserRequestedUnlock() { + public void onUserRequestedUnlock(boolean dismissKeyguard) { } /** @@ -399,26 +428,10 @@ public class TrustAgentService extends Service { } /** - * Call to grant trust on the device. - * - * @param message describes why the device is trusted, e.g. "Trusted by location". - * @param durationMs amount of time in milliseconds to keep the device in a trusted state. - * Trust for this agent will automatically be revoked when the timeout expires unless - * extended by a subsequent call to this function. The timeout is measured from the - * invocation of this function as dictated by {@link SystemClock#elapsedRealtime())}. - * For security reasons, the value should be no larger than necessary. - * The value may be adjusted by the system as necessary to comply with a policy controlled - * by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()} - * for determining when trust expires. - * @param initiatedByUser this is a hint to the system that trust is being granted as the - * direct result of user action - such as solving a security challenge. The hint is used - * by the system to optimize the experience. Behavior may vary by device and release, so - * one should only set this parameter if it meets the above criteria rather than relying on - * the behavior of any particular device or release. Corresponds to - * {@link #FLAG_GRANT_TRUST_INITIATED_BY_USER}. - * @throws IllegalStateException if the agent is not currently managing trust. + * Attempts to grant trust on the device. * - * @deprecated use {@link #grantTrust(CharSequence, long, int)} instead. + * @param initiatedByUser see {@link #FLAG_GRANT_TRUST_INITIATED_BY_USER} + * @deprecated use {@link #grantTrust(CharSequence, long, int, Consumer)} instead. */ @Deprecated public final void grantTrust( @@ -427,7 +440,17 @@ public class TrustAgentService extends Service { } /** - * Call to grant trust on the device. + * Attempts to grant trust on the device. + * @deprecated use {@link #grantTrust(CharSequence, long, int, Consumer)} instead. + */ + @Deprecated + public final void grantTrust( + final CharSequence message, final long durationMs, @GrantTrustFlags final int flags) { + grantTrust(message, durationMs, flags, null); + } + + /** + * Attempts to grant trust on the device. * * @param message describes why the device is trusted, e.g. "Trusted by location". * @param durationMs amount of time in milliseconds to keep the device in a trusted state. @@ -438,19 +461,36 @@ public class TrustAgentService extends Service { * The value may be adjusted by the system as necessary to comply with a policy controlled * by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()} * for determining when trust expires. - * @param flags TBDocumented + * @param flags flags to control call: see constants prefixed by {@code FLAG_GRANT_TRUST_}. + * @param resultCallback may be called with the results of the grant * @throws IllegalStateException if the agent is not currently managing trust. + * + * See {@link GrantTrustResult} for the cases where {@code resultCallback} will be called. */ public final void grantTrust( - final CharSequence message, final long durationMs, @GrantTrustFlags final int flags) { + @NonNull final CharSequence message, + final long durationMs, + @GrantTrustFlags final int flags, + @Nullable final Consumer<GrantTrustResult> resultCallback) { synchronized (mLock) { if (!mManagingTrust) { throw new IllegalStateException("Cannot grant trust if agent is not managing trust." + " Call setManagingTrust(true) first."); } + + // Prepare future for the IPC + AndroidFuture<GrantTrustResult> future = new AndroidFuture<>(); + future.thenAccept(result -> { + if (resultCallback != null) { + // Instead of taking an explicit executor, we post this to mHandler to be + // consistent with the other event methods in this class. + mHandler.post(() -> resultCallback.accept(result)); + } + }); + if (mCallback != null) { try { - mCallback.grantTrust(message.toString(), durationMs, flags); + mCallback.grantTrust(message.toString(), durationMs, flags, future); } catch (RemoteException e) { onError("calling enableTrust()"); } @@ -460,7 +500,7 @@ public class TrustAgentService extends Service { mPendingGrantTrustTask = new Runnable() { @Override public void run() { - grantTrust(message, durationMs, flags); + grantTrust(message, durationMs, flags, resultCallback); } }; } @@ -666,8 +706,14 @@ public class TrustAgentService extends Service { } @Override - public void onUserRequestedUnlock() { - mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK).sendToTarget(); + public void onUserRequestedUnlock(boolean dismissKeyguard) { + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, dismissKeyguard ? 1 : 0, 0) + .sendToTarget(); + } + + @Override + public void onUserMayRequestUnlock() { + mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK).sendToTarget(); } @Override diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index fee23f4b0be7..9ed57c3aedb1 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -103,7 +103,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the - * requested width + * requested width. null if ellipsis is not applied. * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerWidth} is used instead @@ -116,7 +116,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback @NonNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, - boolean includePad, @NonNull TextUtils.TruncateAt ellipsize, + boolean includePad, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) { return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad, ellipsize, ellipsizedWidth, useFallbackLineSpacing); @@ -146,6 +146,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedWidth = outerwidth; mEllipsizedStart = 0; mEllipsizedCount = 0; + mUseFallbackLineSpacing = false; init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); return this; @@ -169,7 +170,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the - * requested width + * requested width. null if ellipsis not applied. * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerWidth} is used instead @@ -181,7 +182,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, - @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) { boolean trust; @@ -200,6 +201,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = false; } + mUseFallbackLineSpacing = useFallbackLineSpacing; + init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing); return this; @@ -252,6 +255,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedWidth = outerwidth; mEllipsizedStart = 0; mEllipsizedCount = 0; + mUseFallbackLineSpacing = false; init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); } @@ -294,7 +298,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the - * requested {@code outerWidth} + * requested {@code outerWidth}. null if ellipsis is not applied. * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerWidth} is used instead @@ -307,7 +311,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback @NonNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult, float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad, - @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) { /* * It is silly to have to call super() and then replaceWith(), @@ -331,6 +335,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = false; } + mUseFallbackLineSpacing = useFallbackLineSpacing; init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing); } diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 43c07c8ab97e..7e0b79470cf2 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -992,6 +992,9 @@ public final class AccessibilityInteractionController { // Unchecked cast - an app that puts other objects in this bundle with this // key will crash. RectF textLocation = ((RectF) textLocations[i]); + if (textLocation == null) { + continue; + } textLocation.scale(applicationScale); if (spec != null) { textLocation.scale(spec.scale); diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 406281d4cade..a13579d0acad 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -401,7 +401,7 @@ public class SurfaceControlViewHost { public void relayout(WindowManager.LayoutParams attrs, WindowlessWindowManager.ResizeCompleteCallback callback) { mViewRoot.setLayoutParams(attrs, false); - mViewRoot.setReportNextDraw(true /* syncBuffer */); + mViewRoot.setReportNextDraw(); mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 22ccaae9e3c3..413855639d09 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -50,7 +50,6 @@ import android.view.accessibility.IAccessibilityEmbeddedConnection; import com.android.internal.view.SurfaceCallbackHelper; import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; @@ -204,6 +203,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private int mSurfaceFlags = SurfaceControl.HIDDEN; + private int mPendingReportDraws; + /** * Transaction that should be used from the render thread. This transaction is only thread safe * with other calls directly from the render thread. @@ -211,6 +212,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private final SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction(); /** + * Used on the main thread to set the transaction that will be synced with the main window. + */ + private final Transaction mSyncTransaction = new Transaction(); + + /** * Transaction that should be used whe * {@link HardwareRenderer.FrameDrawingCallback#onFrameDraw} is invoked. All * frame callbacks can use the same transaction since they will be thread safe @@ -385,12 +391,31 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } - private void performDrawFinished() { - mDrawFinished = true; - if (mAttachedToWindow) { - mParent.requestTransparentRegion(SurfaceView.this); - invalidate(); + private void performDrawFinished(@Nullable Transaction t) { + if (t != null) { + mSyncTransaction.merge(t); + } + + if (mPendingReportDraws > 0) { + mDrawFinished = true; + if (mAttachedToWindow) { + mParent.requestTransparentRegion(SurfaceView.this); + notifyDrawFinished(); + invalidate(); + } + } else { + Log.e(TAG, System.identityHashCode(this) + "finished drawing" + + " but no pending report draw (extra call" + + " to draw completion runnable?)"); + } + } + + void notifyDrawFinished() { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + viewRoot.pendingDrawFinished(mSyncTransaction); } + mPendingReportDraws--; } @Override @@ -413,6 +438,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mGlobalListenersAdded = false; } + while (mPendingReportDraws > 0) { + notifyDrawFinished(); + } + mRequestedVisible = false; updateSurface(); @@ -964,17 +993,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall return; } - final boolean redrawNeeded = sizeChanged || creating || hintChanged - || (mVisible && !mDrawFinished); - final TransactionCallback transactionCallback = - redrawNeeded ? new TransactionCallback() : null; - if (redrawNeeded && viewRoot.wasRelayoutRequested()) { - mBlastBufferQueue.syncNextTransaction( - false /* acquireSingleBuffer */, - transactionCallback::onTransactionReady); - } final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction); + final boolean redrawNeeded = sizeChanged || creating || hintChanged + || (mVisible && !mDrawFinished); try { SurfaceHolder.Callback[] callbacks = null; @@ -993,7 +1015,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mIsCreating = true; if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "visibleChanged -- surfaceCreated"); - callbacks = getSurfaceCallbacks(); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } for (SurfaceHolder.Callback c : callbacks) { c.surfaceCreated(mSurfaceHolder); } @@ -1011,7 +1035,32 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } if (redrawNeeded) { - redrawNeeded(callbacks, transactionCallback); + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceRedrawNeeded"); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + + final boolean wasRelayoutRequested = viewRoot.wasRelayoutRequested(); + if (wasRelayoutRequested && (mBlastBufferQueue != null)) { + mBlastBufferQueue.syncNextTransaction( + false /* acquireSingleBuffer */, + this::onDrawFinished); + } + mPendingReportDraws++; + viewRoot.drawPending(); + SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> { + if (mBlastBufferQueue != null) { + mBlastBufferQueue.stopContinuousSyncTransaction(); + } + // If relayout was requested, then a callback from BBQ will + // be invoked with the sync transaction. onDrawFinished will be + // called in there + if (!wasRelayoutRequested) { + onDrawFinished(null); + } + }); + sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); } } } finally { @@ -1030,64 +1079,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } - private void redrawNeeded(SurfaceHolder.Callback[] callbacks, - @Nullable TransactionCallback transactionCallback) { - if (DEBUG) { - Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded"); - } - final SurfaceHolder.Callback[] capturedCallbacks = - callbacks == null ? getSurfaceCallbacks() : callbacks; - - ViewRootImpl viewRoot = getViewRootImpl(); - boolean isVriSync = viewRoot.addToSync(syncBufferCallback -> - redrawNeededAsync(capturedCallbacks, () -> { - if (mBlastBufferQueue != null) { - mBlastBufferQueue.stopContinuousSyncTransaction(); - } - - Transaction t = null; - if (transactionCallback != null && mBlastBufferQueue != null) { - t = transactionCallback.waitForTransaction(); - } - // If relayout was requested, then a callback from BBQ will - // be invoked with the sync transaction. onDrawFinished will be - // called in there - syncBufferCallback.onBufferReady(t); - onDrawFinished(); - })); - - // If isVriSync, then everything was setup in the addToSync. - if (isVriSync) { - return; - } - - redrawNeededAsync(capturedCallbacks, this::onDrawFinished); - } - - private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks, - Runnable callbacksCollected) { - SurfaceCallbackHelper sch = new SurfaceCallbackHelper(callbacksCollected); - sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); - } - - private static class TransactionCallback { - private final CountDownLatch mCountDownLatch = new CountDownLatch(1); - private Transaction mTransaction; - - Transaction waitForTransaction() { - try { - mCountDownLatch.await(); - } catch (InterruptedException e) { - } - return mTransaction; - } - - void onTransactionReady(Transaction t) { - mTransaction = t; - mCountDownLatch.countDown(); - } - } - /** * Copy the Surface from the SurfaceControl or the blast adapter. * @@ -1198,13 +1189,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat); } - private void onDrawFinished() { + private void onDrawFinished(@Nullable Transaction t) { if (DEBUG) { Log.i(TAG, System.identityHashCode(this) + " " + "finishedDrawing"); } - runOnUiThread(this::performDrawFinished); + runOnUiThread(() -> performDrawFinished(t)); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d15b2a457bfb..172cd03900e7 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -23,8 +23,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsState.ITYPE_IME; -import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; -import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.SIZE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -401,10 +399,8 @@ public final class ViewRootImpl implements ViewParent, private static boolean sAlwaysAssignFocus; /** - * This list must only be modified by the main thread, so a lock is only needed when changing - * the list or when accessing the list from a non-main thread. + * This list must only be modified by the main thread. */ - @GuardedBy("mWindowCallbacks") final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>(); @UnsupportedAppUsage @UiContext @@ -582,15 +578,11 @@ public final class ViewRootImpl implements ViewParent, boolean mReportNextDraw; - /** - * Set whether the draw should send the buffer to system server. When set to true, VRI will - * create a sync transaction with BBQ and send the resulting buffer to system server. If false, - * VRI will not try to sync a buffer in BBQ, but still report when a draw occurred. - * - * Default is true since we normally want to sync the buffer. + * Set whether the draw should use blast sync. This is in case the draw is canceled, + * but will be rescheduled. We still want the next draw to be sync. */ - private boolean mSyncBuffer = true; + boolean mNextDrawUseBlastSync; boolean mFullRedrawNeeded; boolean mNewSurfaceNeeded; @@ -811,10 +803,6 @@ public final class ViewRootImpl implements ViewParent, return mHandwritingInitiator; } - private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer(); - private int mLastSyncId = -1; - private SurfaceSyncer.SyncBufferCallback mSyncBufferCallback; - /** * Keeps track of the last frame number that was attempted to draw. Should only be accessed on * the RenderThread. @@ -981,15 +969,11 @@ public final class ViewRootImpl implements ViewParent, } public void addWindowCallbacks(WindowCallbacks callback) { - synchronized (mWindowCallbacks) { - mWindowCallbacks.add(callback); - } + mWindowCallbacks.add(callback); } public void removeWindowCallbacks(WindowCallbacks callback) { - synchronized (mWindowCallbacks) { - mWindowCallbacks.remove(callback); - } + mWindowCallbacks.remove(callback); } public void reportDrawFinish() { @@ -1711,15 +1695,19 @@ public final class ViewRootImpl implements ViewParent, final boolean forceNextWindowRelayout = args.argi1 != 0; final int displayId = args.argi3; final int resizeMode = args.argi5; - final Rect backdropFrame = frames.backdropFrame; - final boolean frameChanged = !mWinFrame.equals(frames.frame); - final boolean backdropFrameChanged = !mPendingBackDropFrame.equals(backdropFrame); + final Rect frame = frames.frame; + final Rect displayFrame = frames.displayFrame; + if (mTranslator != null) { + mTranslator.translateRectInScreenToAppWindow(frame); + mTranslator.translateRectInScreenToAppWindow(displayFrame); + } + final boolean frameChanged = !mWinFrame.equals(frame); final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration); final boolean displayChanged = mDisplay.getDisplayId() != displayId; final boolean resizeModeChanged = mResizeMode != resizeMode; - if (msg == MSG_RESIZED && !frameChanged && !backdropFrameChanged && !configChanged - && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout) { + if (msg == MSG_RESIZED && !frameChanged && !configChanged && !displayChanged + && !resizeModeChanged && !forceNextWindowRelayout) { return; } @@ -1735,9 +1723,17 @@ public final class ViewRootImpl implements ViewParent, onMovedToDisplay(displayId, mLastConfigurationFromResources); } - setFrame(frames.frame); - mTmpFrames.displayFrame.set(frames.displayFrame); - mPendingBackDropFrame.set(backdropFrame); + setFrame(frame); + mTmpFrames.displayFrame.set(displayFrame); + + if (mDragResizing && mUseMTRenderer) { + boolean fullscreen = frame.equals(mPendingBackDropFrame); + for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { + mWindowCallbacks.get(i).onWindowSizeIsChanging(mPendingBackDropFrame, fullscreen, + mAttachInfo.mVisibleInsets, mAttachInfo.mStableInsets); + } + } + mForceNextWindowRelayout = forceNextWindowRelayout; mPendingAlwaysConsumeSystemBars = args.argi2 != 0; mSyncSeqId = args.argi4; @@ -2318,11 +2314,12 @@ public final class ViewRootImpl implements ViewParent, */ void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible, boolean hasControl) { - if (type != ITYPE_STATUS_BAR && type != ITYPE_NAVIGATION_BAR) { + @InsetsType final int publicType = InsetsState.toPublicType(type); + if (publicType != Type.statusBars() && publicType != Type.navigationBars()) { return; } final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo; - final int systemUiFlag = type == ITYPE_STATUS_BAR + final int systemUiFlag = publicType == Type.statusBars() ? View.SYSTEM_UI_FLAG_FULLSCREEN : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; final boolean wasVisible = (info.globalVisibility & systemUiFlag) == 0; @@ -2893,6 +2890,8 @@ public final class ViewRootImpl implements ViewParent, mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance); } } + final boolean wasReportNextDraw = mReportNextDraw; + boolean useBlastSync = mNextDrawUseBlastSync; if (mFirst || windowShouldResize || viewVisibilityChanged || params != null || mForceNextWindowRelayout) { @@ -2931,6 +2930,9 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "Relayout called with blastSync"); } reportNextDraw(); + if (isHardwareEnabled()) { + useBlastSync = true; + } } final boolean surfaceControlChanged = @@ -3166,7 +3168,7 @@ public final class ViewRootImpl implements ViewParent, // done to achieve a more hermetic fix for S, but it's entirely // possible that checking the most recent value is actually more // correct here. - if (!mStopped || mReportNextDraw) { + if (!mStopped || wasReportNextDraw) { if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || dispatchApplyInsets || updatedConfiguration) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width, @@ -3235,7 +3237,7 @@ public final class ViewRootImpl implements ViewParent, prepareSurfaces(); } - final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); + final boolean didLayout = layoutRequested && (!mStopped || wasReportNextDraw); boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes; if (didLayout) { @@ -3428,40 +3430,51 @@ public final class ViewRootImpl implements ViewParent, mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes); - // If we already got a request for blast sync, then we don't want to unset mSyncBuffer - if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0 - && !mReportNextDraw) { + // Remember if we must report the next draw. + if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { reportNextDraw(); - mSyncBuffer = false; } - boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw(); - if (!cancelAndRedraw) { - createSyncIfNeeded(); + boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; + if (mBLASTDrawConsumer != null) { + useBlastSync = true; } - if (!isViewVisible) { + if (!cancelDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { - mPendingTransitions.get(i).endChangingAnimations(); + mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } - - if (mSyncBufferCallback != null) { - mSyncBufferCallback.onBufferReady(null); - } - } else if (cancelAndRedraw) { - // Try again - scheduleTraversals(); + performDraw(useBlastSync); + mNextDrawUseBlastSync = false; } else { - if (mPendingTransitions != null && mPendingTransitions.size() > 0) { - for (int i = 0; i < mPendingTransitions.size(); ++i) { - mPendingTransitions.get(i).startChangingAnimations(); + if (isViewVisible) { + // Try again + mNextDrawUseBlastSync = useBlastSync; + scheduleTraversals(); + } else { + if (mPendingTransitions != null && mPendingTransitions.size() > 0) { + for (int i = 0; i < mPendingTransitions.size(); ++i) { + mPendingTransitions.get(i).endChangingAnimations(); + } + mPendingTransitions.clear(); + } + + // We may never draw since it's not visible. Report back that we're finished + // drawing. + if (!wasReportNextDraw && mReportNextDraw) { + mReportNextDraw = false; + pendingDrawFinished(); + } + + // Make sure the consumer is not waiting if the view root was just made invisible. + if (mBLASTDrawConsumer != null) { + mBLASTDrawConsumer.accept(null); + mBLASTDrawConsumer = null; } - mPendingTransitions.clear(); } - performDraw(); } if (mAttachInfo.mContentCaptureEvents != null) { @@ -3470,46 +3483,6 @@ public final class ViewRootImpl implements ViewParent, mIsInTraversal = false; mRelayoutRequested = false; - - if (!cancelAndRedraw) { - mReportNextDraw = false; - mSyncBufferCallback = null; - mSyncBuffer = true; - if (mLastSyncId != -1) { - mSurfaceSyncer.markSyncReady(mLastSyncId); - mLastSyncId = -1; - } - } - } - - private void createSyncIfNeeded() { - // Started a sync already. - if (mLastSyncId != -1) { - return; - } - - Consumer<Transaction> syncConsumer = null; - if (mBLASTDrawConsumer != null) { - syncConsumer = mBLASTDrawConsumer; - mBLASTDrawConsumer = null; - } else if (mReportNextDraw) { - syncConsumer = transaction -> { - mSurfaceChangedTransaction.merge(transaction); - reportDrawFinished(); - }; - } - - if (syncConsumer != null) { - final Consumer<Transaction> capturedSyncConsumer = syncConsumer; - mLastSyncId = mSurfaceSyncer.setupSync(transaction -> { - // Callback will be invoked on executor thread so post to main thread. - mHandler.postAtFrontOfQueue(() -> capturedSyncConsumer.accept(transaction)); - }); - if (DEBUG_BLAST) { - Log.d(mTag, "Setup new sync id=" + mLastSyncId); - } - mSurfaceSyncer.addToSync(mLastSyncId, mSyncTarget); - } } private void notifyContentCatpureEvents() { @@ -4123,10 +4096,54 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * A count of the number of calls to pendingDrawFinished we + * require to notify the WM drawing is complete. + */ + int mDrawsNeededToReport = 0; + + /** + * Delay notifying WM of draw finished until + * a balanced call to pendingDrawFinished. + */ + void drawPending() { + mDrawsNeededToReport++; + } + + void pendingDrawFinished(Transaction t) { + if (mDrawsNeededToReport == 0) { + throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls"); + } + + if (t != null) { + if (DEBUG_BLAST) { + Log.d(mTag, "Merging transaction into main window transaction"); + } + mSurfaceChangedTransaction.merge(t); + } + + mDrawsNeededToReport--; + if (mDrawsNeededToReport == 0) { + reportDrawFinished(); + } else if (DEBUG_BLAST) { + Log.d(mTag, "pendingDrawFinished. Waiting on draw reported mDrawsNeededToReport=" + + mDrawsNeededToReport); + } + } + + void pendingDrawFinished() { + pendingDrawFinished(null); + } + + private void postDrawFinished() { + mHandler.sendEmptyMessage(MSG_DRAW_FINISHED); + } + private void reportDrawFinished() { if (DEBUG_BLAST) { - Log.d(mTag, "reportDrawFinished " + Debug.getCallers(5)); + Log.d(mTag, "reportDrawFinished"); } + mDrawsNeededToReport = 0; try { mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction, Integer.MAX_VALUE); @@ -4145,14 +4162,6 @@ public final class ViewRootImpl implements ViewParent, return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled(); } - boolean addToSync(SurfaceSyncer.SyncTarget syncable) { - if (mLastSyncId == -1) { - return false; - } - mSurfaceSyncer.addToSync(mLastSyncId, syncable); - return true; - } - private void addFrameCommitCallbackIfNeeded() { if (!isHardwareEnabled()) { return; @@ -4183,81 +4192,188 @@ public final class ViewRootImpl implements ViewParent, }); } + private HardwareRenderer.FrameCommitCallback createFrameCommitCallbackForSync( + boolean useBlastSync, boolean reportNextDraw, Consumer<Transaction> blastSyncConsumer) { + return didProduceBuffer -> { + if (DEBUG_BLAST) { + Log.d(mTag, "Received frameCommittedCallback " + + " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum + + " didProduceBuffer=" + didProduceBuffer); + } + + // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next + // draw attempt. The next transaction and transaction complete callback were only set + // for the current draw attempt. + final Transaction pendingTransactions; + if (!didProduceBuffer) { + mBlastBufferQueue.syncNextTransaction(null); + // Get the transactions that were sent to mergeWithNextTransaction since the + // frame didn't draw on this vsync. It's possible the frame will draw later, but + // it's better to not be sync than to block on a frame that may never come. + pendingTransactions = mBlastBufferQueue.gatherPendingTransactions( + mRtLastAttemptedDrawFrameNum); + if (!useBlastSync && !reportNextDraw) { + pendingTransactions.apply(); + } + } else { + pendingTransactions = null; + } + // Post at front of queue so the buffer can be processed immediately and allow RT + // to continue processing new buffers. If RT tries to process buffers before the sync + // buffer is applied, the new buffers will not get acquired and could result in a + // deadlock. UI thread would wait on RT, but RT would be blocked waiting for a free + // buffer. + mHandler.postAtFrontOfQueue(() -> { + if (!didProduceBuffer && useBlastSync) { + mSurfaceChangedTransaction.merge(pendingTransactions); + if (blastSyncConsumer != null) { + blastSyncConsumer.accept(mSurfaceChangedTransaction); + } + } + + // This is to ensure pendingDrawFinished is only called exactly one time per draw + // attempt when reportNextDraw is true. Since, we sometimes create a sync + // transaction callback, the callback will handle calling pendingDrawFinished. + // However, there are cases where the transaction callback may not be called. + // 1. If useBlastSync is false, then we know that a sync transaction callback was + // not created so we won't invoke pendingDrawFinished there. + // 2. If the draw didn't produce a frame, didProduceBuffer == false, then we know + // the sync transaction callback will not be invoked even if one was set up. + if (reportNextDraw && (!didProduceBuffer || !useBlastSync)) { + pendingDrawFinished(); + } + }); + + }; + } + @Nullable - private void registerFrameDrawingCallbackForBlur() { + private FrameDrawingCallback createFrameDrawingCallbackIfNeeded(boolean useBlastSync, + boolean reportNextDraw) { if (!isHardwareEnabled()) { - return; + return null; } final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates(); final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions(); - if (!needsCallbackForBlur) { - return; + if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw && !mHasPendingTransactions) { + return null; + } + + final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer; + mBLASTDrawConsumer = null; + + if (DEBUG_BLAST) { + Log.d(mTag, "Creating frameDrawingCallback" + + " nextDrawUseBlastSync=" + useBlastSync + + " reportNextDraw=" + reportNextDraw + + " hasBlurUpdates=" + hasBlurUpdates + + " hasBlastSyncConsumer=" + (blastSyncConsumer != null) + + " mHasPendingTransactions=" + mHasPendingTransactions); } final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame = - mBlurRegionAggregator.getBlurRegionsCopyForRT(); + needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null; + final boolean hasPendingTransactions = mHasPendingTransactions; + mHasPendingTransactions = false; + // The callback will run on the render thread. - registerRtFrameCallback((frame) -> mBlurRegionAggregator - .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates)); - } + return new FrameDrawingCallback() { + @Override + public void onFrameDraw(long frame) { + } - private void registerCallbackForPendingTransactions() { - registerRtFrameCallback(new FrameDrawingCallback() { @Override public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) { + if (DEBUG_BLAST) { + Log.d(mTag, + "Received frameDrawingCallback syncResult=" + syncResult + " frameNum=" + + frame + "."); + } + + mRtLastAttemptedDrawFrameNum = frame; + + if (needsCallbackForBlur) { + mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame, + blurRegionsForFrame, hasBlurUpdates); + } + + if (mBlastBufferQueue == null) { + return null; + } + + if (!useBlastSync && !reportNextDraw && !hasPendingTransactions) { + return null; + } + + // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or + // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up + // any blast sync or commit callback, and the code should directly call + // pendingDrawFinished. if ((syncResult & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) { - mBlastBufferQueue.applyPendingTransactions(frame); + if (reportNextDraw) { + mHandler.postAtFrontOfQueue(() -> pendingDrawFinished()); + } return null; } - return didProduceBuffer -> { - if (!didProduceBuffer) { - mBlastBufferQueue.applyPendingTransactions(frame); - } - }; + if (DEBUG_BLAST) { + Log.d(mTag, "Setting up sync and frameCommitCallback"); + } - } + if (useBlastSync) { + // Frame callbacks will always occur after submitting draw requests and before + // the draw actually occurs. This will ensure that we set the next transaction + // for the frame that's about to get drawn and not on a previous frame. + mBlastBufferQueue.syncNextTransaction( + t -> { + mHandler.postAtFrontOfQueue(() -> { + mSurfaceChangedTransaction.merge(t); + if (blastSyncConsumer != null) { + blastSyncConsumer.accept(mSurfaceChangedTransaction); + } - @Override - public void onFrameDraw(long frame) { + if (reportNextDraw) { + pendingDrawFinished(); + } + }); + }); + } + + return createFrameCommitCallbackForSync(useBlastSync, reportNextDraw, + blastSyncConsumer); } - }); + }; } - private void performDraw() { + private void performDraw(boolean useBlastSync) { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { return; } else if (mView == null) { return; } - final boolean fullRedrawNeeded = mFullRedrawNeeded || mSyncBufferCallback != null; + final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw || useBlastSync; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); - registerFrameDrawingCallbackForBlur(); - addFrameCommitCallbackIfNeeded(); - - boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null; - if (usingAsyncReport) { - registerCallbacksForSync(mSyncBuffer, mSyncBufferCallback); - } else if (mHasPendingTransactions) { - // These callbacks are only needed if there's no sync involved and there were calls to - // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and - // apply those transactions directly so they don't get stuck forever. - registerCallbackForPendingTransactions(); + FrameDrawingCallback frameDrawingCallback = createFrameDrawingCallbackIfNeeded(useBlastSync, + mReportNextDraw); + if (frameDrawingCallback != null) { + mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frameDrawingCallback); } - mHasPendingTransactions = false; + addFrameCommitCallbackIfNeeded(); + boolean usingAsyncReport = isHardwareEnabled() && (useBlastSync || mReportNextDraw); try { boolean canUseAsync = draw(fullRedrawNeeded); if (usingAsyncReport && !canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCallback(null); usingAsyncReport = false; + mAttachInfo.mThreadedRenderer.unregisterRtFrameCallback(frameDrawingCallback); } } finally { mIsDrawing = false; @@ -4275,6 +4391,7 @@ public final class ViewRootImpl implements ViewParent, } if (mReportNextDraw) { + mReportNextDraw = false; // if we're using multi-thread renderer, wait for the window frame draws if (mWindowDrawCountDown != null) { @@ -4295,11 +4412,7 @@ public final class ViewRootImpl implements ViewParent, } if (mSurfaceHolder != null && mSurface.isValid()) { - final SurfaceSyncer.SyncBufferCallback syncBufferCallback = mSyncBufferCallback; - SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> - mHandler.post(() -> syncBufferCallback.onBufferReady(null))); - mSyncBufferCallback = null; - + SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); @@ -4307,11 +4420,9 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.fence(); } + pendingDrawFinished(); } } - if (mSyncBufferCallback != null && !usingAsyncReport) { - mSyncBufferCallback.onBufferReady(null); - } if (mPerformContentCapture) { performContentCaptureInitialReport(); } @@ -5320,6 +5431,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26; private static final int MSG_UPDATE_POINTER_ICON = 27; private static final int MSG_POINTER_CAPTURE_CHANGED = 28; + private static final int MSG_DRAW_FINISHED = 29; private static final int MSG_INSETS_CHANGED = 30; private static final int MSG_INSETS_CONTROL_CHANGED = 31; private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32; @@ -5382,6 +5494,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_UPDATE_POINTER_ICON"; case MSG_POINTER_CAPTURE_CHANGED: return "MSG_POINTER_CAPTURE_CHANGED"; + case MSG_DRAW_FINISHED: + return "MSG_DRAW_FINISHED"; case MSG_INSETS_CHANGED: return "MSG_INSETS_CHANGED"; case MSG_INSETS_CONTROL_CHANGED: @@ -5510,8 +5624,6 @@ public final class ViewRootImpl implements ViewParent, mTmpFrames.frame.top = t; mTmpFrames.frame.bottom = t + h; setFrame(mTmpFrames.frame); - - mPendingBackDropFrame.set(mWinFrame); maybeHandleWindowMove(mWinFrame); } break; @@ -5614,6 +5726,9 @@ public final class ViewRootImpl implements ViewParent, final boolean hasCapture = msg.arg1 != 0; handlePointerCaptureChanged(hasCapture); } break; + case MSG_DRAW_FINISHED: { + pendingDrawFinished(); + } break; case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: { systemGestureExclusionChanged(); } break; @@ -8006,7 +8121,6 @@ public final class ViewRootImpl implements ViewParent, getConfiguration().windowConfiguration.getBounds()); } - mPendingBackDropFrame.set(mTmpFrames.backdropFrame); if (mSurfaceControl.isValid()) { if (!useBLAST()) { mSurface.copyFrom(mSurfaceControl); @@ -8037,6 +8151,7 @@ public final class ViewRootImpl implements ViewParent, if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame); + mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame); mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets); mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls); } @@ -8081,6 +8196,13 @@ public final class ViewRootImpl implements ViewParent, private void setFrame(Rect frame) { mWinFrame.set(frame); + + // Surface position is now inherited from parent, and BackdropFrameRenderer uses backdrop + // frame to position content. Thus, we just keep the size of backdrop frame, and remove the + // offset to avoid double offset from display origin. + mPendingBackDropFrame.set(frame); + mPendingBackDropFrame.offsetTo(0, 0); + mInsetsController.onFrameChanged(mOverrideInsetsFrame != null ? mOverrideInsetsFrame : frame); } @@ -8472,28 +8594,7 @@ public final class ViewRootImpl implements ViewParent, private void dispatchResized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) { - final Rect frame = frames.frame; - final Rect backDropFrame = frames.backdropFrame; - if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString() - + " reportDraw=" + reportDraw - + " backDropFrame=" + backDropFrame); - - // Tell all listeners that we are resizing the window so that the chrome can get - // updated as fast as possible on a separate thread, - if (mDragResizing && mUseMTRenderer) { - boolean fullscreen = frame.equals(backDropFrame); - synchronized (mWindowCallbacks) { - for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { - mWindowCallbacks.get(i).onWindowSizeIsChanging(backDropFrame, fullscreen, - mAttachInfo.mVisibleInsets, mAttachInfo.mStableInsets); - } - } - } - Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED); - if (mTranslator != null) { - mTranslator.translateRectInScreenToAppWindow(frame); - } SomeArgs args = SomeArgs.obtain(); final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid()); args.arg1 = sameProcessCall ? new ClientWindowFrames(frames) : frames; @@ -9795,8 +9896,8 @@ public final class ViewRootImpl implements ViewParent, } private void reportNextDraw() { - if (DEBUG_BLAST) { - Log.d(mTag, "reportNextDraw " + Debug.getCallers(5)); + if (mReportNextDraw == false) { + drawPending(); } mReportNextDraw = true; } @@ -9807,14 +9908,9 @@ public final class ViewRootImpl implements ViewParent, * This method is only supposed to be used to speed up the interaction from SystemUI and window * manager when waiting for the first frame to be drawn when turning on the screen. DO NOT USE * unless you fully understand this interaction. - * - * @param syncBuffer If true, the transaction that contains the buffer from the draw should be - * sent to system to be synced. If false, VRI will not try to sync the buffer, - * but only report back that a buffer was drawn. * @hide */ - public void setReportNextDraw(boolean syncBuffer) { - mSyncBuffer = syncBuffer; + public void setReportNextDraw() { reportNextDraw(); invalidate(); } @@ -10840,15 +10936,14 @@ public final class ViewRootImpl implements ViewParent, return mWindowSession; } - private void registerCallbacksForSync(boolean syncBuffer, + private void registerCallbacksForSync( final SurfaceSyncer.SyncBufferCallback syncBufferCallback) { if (!isHardwareEnabled()) { + // TODO: correctly handle when hardware disabled + syncBufferCallback.onBufferReady(null); return; } - if (DEBUG_BLAST) { - Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer); - } mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { @@ -10877,9 +10972,7 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "Setting up sync and frameCommitCallback"); } - if (syncBuffer) { - mBlastBufferQueue.syncNextTransaction(syncBufferCallback::onBufferReady); - } + mBlastBufferQueue.syncNextTransaction(t -> syncBufferCallback.onBufferReady(t)); return didProduceBuffer -> { if (DEBUG_BLAST) { @@ -10893,40 +10986,18 @@ public final class ViewRootImpl implements ViewParent, // were only set for the current draw attempt. if (!didProduceBuffer) { mBlastBufferQueue.syncNextTransaction(null); - // Gather the transactions that were sent to mergeWithNextTransaction // since the frame didn't draw on this vsync. It's possible the frame will // draw later, but it's better to not be sync than to block on a frame that // may never come. syncBufferCallback.onBufferReady( mBlastBufferQueue.gatherPendingTransactions(frame)); - return; - } - - // If we didn't request to sync a buffer, then we won't get the - // syncNextTransaction callback. Instead, just report back to the Syncer so it - // knows that this sync request is complete. - if (!syncBuffer) { - syncBufferCallback.onBufferReady(null); } }; } }); } - public final SurfaceSyncer.SyncTarget mSyncTarget = this::readyToSync; - - private void readyToSync(SurfaceSyncer.SyncBufferCallback syncBufferCallback) { - if (mSyncBufferCallback != null) { - Log.d(mTag, "Already set sync for the next draw."); - mSyncBufferCallback.onBufferReady(null); - } - if (DEBUG_BLAST) { - Log.d(mTag, "Setting syncFrameCallback"); - } - mSyncBufferCallback = syncBufferCallback; - if (!mIsInTraversal && !mTraversalScheduled) { - scheduleTraversals(); - } - } + public final SurfaceSyncer.SyncTarget mSyncTarget = + syncBufferCallback -> registerCallbacksForSync(syncBufferCallback); } diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index a3b1313202c2..b7b71f123dad 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -179,6 +179,8 @@ public class WindowLayout { final boolean hasCompatScale = compatScale != 1f; final int pw = outParentFrame.width(); final int ph = outParentFrame.height(); + final boolean extendedByCutout = + (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0; int rw = requestedWidth; int rh = requestedHeight; float x, y; @@ -186,11 +188,13 @@ public class WindowLayout { // If the view hierarchy hasn't been measured, the requested width and height would be // UNSPECIFIED_LENGTH. This can happen in the first layout of a window or in the simulated - // layout. - if (rw == UNSPECIFIED_LENGTH) { + // layout. If extendedByCutout is true, we cannot use the requested lengths. Otherwise, + // the window frame might be extended again because the requested lengths may come from the + // window frame. + if (rw == UNSPECIFIED_LENGTH || extendedByCutout) { rw = attrs.width >= 0 ? attrs.width : pw; } - if (rh == UNSPECIFIED_LENGTH) { + if (rh == UNSPECIFIED_LENGTH || extendedByCutout) { rh = attrs.height >= 0 ? attrs.height : ph; } @@ -262,29 +266,15 @@ public class WindowLayout { Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame); } - if ((attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0 - && !cutout.isEmpty()) { - // If the actual frame covering a display cutout, and the window is requesting to extend - // it's requested frame, re-do the frame calculation after getting the new requested - // size. + if (extendedByCutout && !displayCutoutSafe.contains(outFrame)) { mTempRect.set(outFrame); - // Do nothing if the display cutout and window don't overlap entirely. This may happen - // when the cutout is not on the same side with the window. - boolean shouldExpand = false; - final Rect [] cutoutBounds = cutout.getBoundingRectsAll(); - for (Rect cutoutBound : cutoutBounds) { - if (cutoutBound.isEmpty()) continue; - if (mTempRect.contains(cutoutBound) || cutoutBound.contains(mTempRect)) { - shouldExpand = true; - break; - } - } - if (shouldExpand) { - // Try to fit move the bar to avoid the display cutout first. Make sure the clip - // flags are not set to make the window move. - final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL; - Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe, - mTempRect); + + // Move the frame into displayCutoutSafe. + final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL; + Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe, + mTempRect); + + if (mTempRect.intersect(outDisplayFrame)) { outFrame.union(mTempRect); } } diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index fadbdbbe8746..464414d861fd 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -646,11 +646,9 @@ public abstract class Animation implements Cloneable { * @param bg The background color. If 0, no background. Currently must * be black, with any desired alpha level. * - * @deprecated None of window animations are running with background color. */ - @Deprecated public void setBackgroundColor(@ColorInt int bg) { - // The background color is not needed any more, do nothing. + mBackgroundColor = bg; } /** @@ -824,13 +822,10 @@ public abstract class Animation implements Cloneable { /** * Returns the background color behind the animation. - * - * @deprecated None of window animations are running with background color. */ - @Deprecated @ColorInt public int getBackgroundColor() { - return 0; + return mBackgroundColor; } /** diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index fd336a27bb67..6209b46997e8 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -414,4 +414,12 @@ public interface InputMethod { // intentionally empty } + /** + * Finish stylus handwriting session. + * @hide + */ + default void finishStylusHandwriting() { + // intentionally empty + } + } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 7f8a68dbd0ea..67f284b437b0 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -6309,7 +6309,8 @@ public class Editor { } switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: - if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (event.isFromSource(InputDevice.SOURCE_MOUSE) + || (mTextView.isAutoHandwritingEnabled() && isFromStylus(event))) { break; } if (mIsDraggingCursor) { @@ -6332,6 +6333,11 @@ public class Editor { } } + private boolean isFromStylus(MotionEvent motionEvent) { + final int pointerIndex = motionEvent.getActionIndex(); + return motionEvent.getToolType(pointerIndex) == MotionEvent.TOOL_TYPE_STYLUS; + } + private void positionCursorDuringDrag(MotionEvent event) { mPrevLineDuringDrag = getLineDuringDrag(event); int offset = mTextView.getOffsetAtCoordinate(mPrevLineDuringDrag, event.getX()); diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index 5b915cc22ec5..51f3fe2551ad 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -40,9 +40,6 @@ public class ClientWindowFrames implements Parcelable { */ public final @NonNull Rect parentFrame = new Rect(); - /** The background area while the window is resizing. */ - public final @NonNull Rect backdropFrame = new Rect(); - public boolean isParentFrameClippedByDisplayCutout; public ClientWindowFrames() { @@ -52,7 +49,6 @@ public class ClientWindowFrames implements Parcelable { frame.set(other.frame); displayFrame.set(other.displayFrame); parentFrame.set(other.parentFrame); - backdropFrame.set(other.backdropFrame); isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout; } @@ -65,7 +61,6 @@ public class ClientWindowFrames implements Parcelable { frame.readFromParcel(in); displayFrame.readFromParcel(in); parentFrame.readFromParcel(in); - backdropFrame.readFromParcel(in); isParentFrameClippedByDisplayCutout = in.readBoolean(); } @@ -74,7 +69,6 @@ public class ClientWindowFrames implements Parcelable { frame.writeToParcel(dest, flags); displayFrame.writeToParcel(dest, flags); parentFrame.writeToParcel(dest, flags); - backdropFrame.writeToParcel(dest, flags); dest.writeBoolean(isParentFrameClippedByDisplayCutout); } @@ -84,7 +78,6 @@ public class ClientWindowFrames implements Parcelable { return "ClientWindowFrames{frame=" + frame.toShortString(sb) + " display=" + displayFrame.toShortString(sb) + " parentFrame=" + parentFrame.toShortString(sb) - + " backdrop=" + backdropFrame.toShortString(sb) + " parentClippedByDisplayCutout=" + isParentFrameClippedByDisplayCutout + "}"; } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index fab180dae0bf..f1c0d8dee525 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -100,8 +100,12 @@ public interface SplashScreen { * <p> * To reset to the default theme, set this the themeId to {@link Resources#ID_NULL}. * <p> - * <b>Note:</b> The theme name must be stable across versions, otherwise it won't be found - * after your application is updated. + * <b>Note:</b> Internally, the theme name is resolved and persisted. This means that the theme + * name must be stable across versions, otherwise it won't be found after your application is + * updated. + * + * @param themeId The ID of the splashscreen theme to be used in place of the one defined in + * the manifest. */ void setSplashScreenTheme(@StyleRes int themeId); diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 10d7ecab4ffa..19ee1d046184 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -233,14 +233,6 @@ public final class SplashScreenView extends FrameLayout { } /** - * Set the animation duration if icon is animatable. - */ - public Builder setAnimationDurationMillis(long duration) { - mIconAnimationDuration = Duration.ofMillis(duration); - return this; - } - - /** * Set the Runnable that can receive the task which should be executed on UI thread. * @param uiThreadInitTask */ @@ -294,8 +286,7 @@ public final class SplashScreenView extends FrameLayout { } else { view.mIconView = createSurfaceView(view); } - view.initIconAnimation(mIconDrawable, - mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0); + view.initIconAnimation(mIconDrawable); view.mIconAnimationStart = mIconAnimationStart; view.mIconAnimationDuration = mIconAnimationDuration; } else if (mIconSize != 0) { @@ -463,6 +454,11 @@ public final class SplashScreenView extends FrameLayout { /** * Returns the duration of the icon animation if icon is animatable. * + * Note the return value can be null or 0 if the + * {@link android.R.attr#windowSplashScreenAnimatedIcon} is not + * {@link android.graphics.drawable.AnimationDrawable} or + * {@link android.graphics.drawable.AnimatedVectorDrawable}. + * * @see android.R.attr#windowSplashScreenAnimatedIcon * @see android.R.attr#windowSplashScreenAnimationDuration */ @@ -497,12 +493,12 @@ public final class SplashScreenView extends FrameLayout { mSurfaceView.setChildSurfacePackage(mSurfacePackage); } - void initIconAnimation(Drawable iconDrawable, long duration) { + void initIconAnimation(Drawable iconDrawable) { if (!(iconDrawable instanceof IconAnimateListener)) { return; } IconAnimateListener aniDrawable = (IconAnimateListener) iconDrawable; - aniDrawable.prepareAnimate(duration, this::animationStartCallback); + aniDrawable.prepareAnimate(this::animationStartCallback); aniDrawable.setAnimationJankMonitoring(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { @@ -524,7 +520,7 @@ public final class SplashScreenView extends FrameLayout { private void animationStartCallback(long animDuration) { mIconAnimationStart = Instant.now(); - if (animDuration > 0) { + if (animDuration >= 0) { mIconAnimationDuration = Duration.ofMillis(animDuration); } } @@ -695,10 +691,9 @@ public final class SplashScreenView extends FrameLayout { public interface IconAnimateListener { /** * Prepare the animation if this drawable also be animatable. - * @param duration The animation duration. * @param startListener The callback listener used to receive the start of the animation. */ - void prepareAnimate(long duration, LongConsumer startListener); + void prepareAnimate(LongConsumer startListener); /** * Stop animation. diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java new file mode 100644 index 000000000000..76e58988eedf --- /dev/null +++ b/core/java/com/android/internal/app/AppLocaleStore.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.app.LocaleConfig; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.LocaleList; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Locale; + +public class AppLocaleStore { + private static final String TAG = AppLocaleStore.class.getSimpleName(); + + public static ArrayList<Locale> getAppSupportedLocales(Context context, String packageName) { + ArrayList<Locale> appSupportedLocales = new ArrayList<>(); + LocaleList packageLocaleList = getPackageLocales(context, packageName); + + if (packageLocaleList != null && packageLocaleList.size() > 0) { + for (int i = 0; i < packageLocaleList.size(); i++) { + appSupportedLocales.add(packageLocaleList.get(i)); + } + Log.d(TAG, "getAppSupportedLocales from LocaleConfig. Size: " + + appSupportedLocales.size()); + } else { + String[] languages = getAssetLocales(context, packageName); + for (String language : languages) { + appSupportedLocales.add(Locale.forLanguageTag(language)); + } + Log.d(TAG, "getAppSupportedLocales from asset. Size: " + + appSupportedLocales.size()); + } + return appSupportedLocales; + } + + private static LocaleList getPackageLocales(Context context, String packageName) { + try { + LocaleConfig localeConfig = + new LocaleConfig(context.createPackageContext(packageName, 0)); + if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { + return localeConfig.getSupportedLocales(); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + packageName + " / " + e); + } + return null; + } + + private static String[] getAssetLocales(Context context, String packageName) { + try { + PackageManager packageManager = context.getPackageManager(); + String[] locales = packageManager.getResourcesForApplication( + packageManager.getPackageInfo(packageName, PackageManager.MATCH_ALL) + .applicationInfo).getAssets().getNonSystemLocales(); + if (locales == null) { + Log.i(TAG, "[" + packageName + "] locales are null."); + return new String[0]; + } else if (locales.length <= 0) { + Log.i(TAG, "[" + packageName + "] locales length is 0."); + return new String[0]; + } + return locales; + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + packageName + " / " + e); + } + return new String[0]; + } + +} diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 70506ccaebb8..c2e145eccb97 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -107,7 +107,6 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.Space; import android.widget.TextView; -import android.widget.Toast; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -1054,7 +1053,6 @@ public class ChooserActivity extends ResolverActivity implements ClipboardManager clipboardManager = (ClipboardManager) getSystemService( Context.CLIPBOARD_SERVICE); clipboardManager.setPrimaryClipAsPackage(clipData, getReferrerPackageName()); - Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show(); // Log share completion via copy LogMaker targetLogMaker = new LogMaker( diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java index 707286e0fc4c..57bd3f945358 100644 --- a/core/java/com/android/internal/app/LocaleHelper.java +++ b/core/java/com/android/internal/app/LocaleHelper.java @@ -234,7 +234,11 @@ public class LocaleHelper { public int compare(LocaleStore.LocaleInfo lhs, LocaleStore.LocaleInfo rhs) { // We don't care about the various suggestion types, just "suggested" (!= 0) // and "all others" (== 0) - if (lhs.isSuggested() == rhs.isSuggested()) { + if (lhs.isAppCurrentLocale() || rhs.isAppCurrentLocale()) { + return lhs.isAppCurrentLocale() ? -1 : 1; + } else if (lhs.isSystemLocale() || rhs.isSystemLocale()) { + return lhs.isSystemLocale() ? -1 : 1; + } else if (lhs.isSuggested() == rhs.isSuggested()) { // They are in the same "bucket" (suggested / others), so we compare the text return mCollator.compare( removePrefixForCompare(lhs.getLocale(), lhs.getLabel(mCountryMode)), diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index b4ae56f23443..e7cb43e019ad 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -23,6 +23,7 @@ import android.content.Context; import android.os.Bundle; import android.os.LocaleList; import android.text.TextUtils; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -32,6 +33,7 @@ import android.widget.SearchView; import com.android.internal.R; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Locale; @@ -45,6 +47,7 @@ import java.util.Set; * default locale.</p> */ public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener { + private static final String TAG = LocalePickerWithRegion.class.getSimpleName(); private static final String PARENT_FRAGMENT_NAME = "localeListEditor"; private SuggestedLocaleAdapter mAdapter; @@ -57,6 +60,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O private boolean mPreviousSearchHadFocus = false; private int mFirstVisiblePosition = 0; private int mTopDistance = 0; + private String mAppPackageName; /** * Other classes can register to be notified when a locale was selected. @@ -73,17 +77,25 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O private static LocalePickerWithRegion createCountryPicker(Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, - boolean translatedOnly) { + boolean translatedOnly, String appPackageName) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); boolean shouldShowTheList = localePicker.setListener(context, listener, parent, - translatedOnly); + translatedOnly, appPackageName); return shouldShowTheList ? localePicker : null; } public static LocalePickerWithRegion createLanguagePicker(Context context, LocaleSelectedListener listener, boolean translatedOnly) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); - localePicker.setListener(context, listener, /* parent */ null, translatedOnly); + localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null); + return localePicker; + } + + public static LocalePickerWithRegion createLanguagePicker(Context context, + LocaleSelectedListener listener, boolean translatedOnly, String appPackageName) { + LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); + localePicker.setListener( + context, listener, /* parent */ null, translatedOnly, appPackageName); return localePicker; } @@ -101,20 +113,32 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O * "pretending" it was selected, and return false.</p> */ private boolean setListener(Context context, LocaleSelectedListener listener, - LocaleStore.LocaleInfo parent, boolean translatedOnly) { + LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) { this.mParentLocale = parent; this.mListener = listener; this.mTranslatedOnly = translatedOnly; + this.mAppPackageName = appPackageName; setRetainInstance(true); final HashSet<String> langTagsToIgnore = new HashSet<>(); - if (!translatedOnly) { + LocaleStore.LocaleInfo appCurrentLocale = + LocaleStore.getAppCurrentLocaleInfo(context, appPackageName); + boolean isForCountryMode = parent != null; + + if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) { + if (appCurrentLocale != null) { + Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag()); + langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag()); + } else { + Log.d(TAG, "appCurrentLocale is null"); + } + } else if (!translatedOnly) { final LocaleList userLocales = LocalePicker.getLocales(); final String[] langTags = userLocales.toLanguageTags().split(","); Collections.addAll(langTagsToIgnore, langTags); } - if (parent != null) { + if (isForCountryMode) { mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, parent, translatedOnly); if (mLocaleList.size() <= 1) { @@ -127,10 +151,39 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, null /* no parent */, translatedOnly); } + Log.d(TAG, "mLocaleList size: " + mLocaleList.size()); + + // Adding current locale and system default option into suggestion list + if(!TextUtils.isEmpty(appPackageName)) { + if (appCurrentLocale != null && !isForCountryMode) { + mLocaleList.add(appCurrentLocale); + } + filterTheLanguagesNotSupportedInApp(context, appPackageName); + if (!isForCountryMode) { + mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo()); + } + } return true; } + private void filterTheLanguagesNotSupportedInApp(Context context, String appPackageName) { + ArrayList<Locale> supportedLocales = + AppLocaleStore.getAppSupportedLocales(context, appPackageName); + + Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>(); + for(LocaleStore.LocaleInfo li: mLocaleList) { + for(Locale l: supportedLocales) { + if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) { + filteredList.add(li); + } + } + } + Log.d(TAG, "mLocaleList after app-supported filter: " + filteredList.size()); + + mLocaleList = filteredList; + } + private void returnToParentFrame() { getFragmentManager().popBackStack(PARENT_FRAGMENT_NAME, FragmentManager.POP_BACK_STACK_INCLUSIVE); @@ -151,7 +204,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O final boolean countryMode = mParentLocale != null; final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault(); - mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode); + mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName); final LocaleHelper.LocaleInfoComparator comp = new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode); mAdapter.sort(comp); @@ -212,17 +265,22 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O @Override public void onListItemClick(ListView l, View v, int position, long id) { + SuggestedLocaleAdapter adapter = (SuggestedLocaleAdapter) getListAdapter(); final LocaleStore.LocaleInfo locale = - (LocaleStore.LocaleInfo) getListAdapter().getItem(position); + (LocaleStore.LocaleInfo) adapter.getItem(position); + // Special case for resetting the app locale to equal the system locale. + boolean isSystemLocale = locale.isSystemLocale(); + boolean isRegionLocale = locale.getParent() != null; - if (locale.getParent() != null) { + if (isSystemLocale || isRegionLocale) { if (mListener != null) { mListener.onLocaleSelected(locale); } returnToParentFrame(); } else { LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( - getContext(), mListener, locale, mTranslatedOnly /* translate only */); + getContext(), mListener, locale, mTranslatedOnly /* translate only */, + adapter.getAppPackageName()); if (selector != null) { getFragmentManager().beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java index 1c5ca593ebba..cea8eaa3ee7f 100644 --- a/core/java/com/android/internal/app/LocaleStore.java +++ b/core/java/com/android/internal/app/LocaleStore.java @@ -16,11 +16,13 @@ package com.android.internal.app; +import android.app.LocaleManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.LocaleList; import android.provider.Settings; import android.telephony.TelephonyManager; +import android.util.Log; import java.io.Serializable; import java.util.HashMap; @@ -31,12 +33,17 @@ import java.util.Set; public class LocaleStore { private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>(); + private static final String TAG = LocaleStore.class.getSimpleName(); private static boolean sFullyInitialized = false; public static class LocaleInfo implements Serializable { private static final int SUGGESTION_TYPE_NONE = 0; private static final int SUGGESTION_TYPE_SIM = 1 << 0; private static final int SUGGESTION_TYPE_CFG = 1 << 1; + // Only for per-app language picker + private static final int SUGGESTION_TYPE_CURRENT = 1 << 2; + // Only for per-app language picker + private static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3; private final Locale mLocale; private final Locale mParent; @@ -189,6 +196,14 @@ public class LocaleStore { public void setChecked(boolean checked) { mIsChecked = checked; } + + public boolean isAppCurrentLocale() { + return (mSuggestionFlags & SUGGESTION_TYPE_CURRENT) > 0; + } + + public boolean isSystemLocale() { + return (mSuggestionFlags & SUGGESTION_TYPE_SYSTEM_LANGUAGE) > 0; + } } private static Set<String> getSimCountries(Context context) { @@ -239,6 +254,40 @@ public class LocaleStore { } } + public static LocaleInfo getAppCurrentLocaleInfo(Context context, String appPackageName) { + if (appPackageName == null) { + return null; + } + + LocaleManager localeManager = context.getSystemService(LocaleManager.class); + try { + LocaleList localeList = (localeManager == null) + ? null : localeManager.getApplicationLocales(appPackageName); + Locale locale = localeList == null ? null : localeList.get(0); + + if (locale != null) { + LocaleInfo localeInfo = new LocaleInfo(locale); + localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT; + localeInfo.mIsTranslated = true; + return localeInfo; + } + } catch (IllegalArgumentException e) { + Log.d(TAG, "IllegalArgumentException ", e); + } + return null; + } + + /** + * The "system default" is special case for per-app picker. Intentionally keep the locale + * empty to let activity know "system default" been selected. + */ + public static LocaleInfo getSystemDefaultLocaleInfo() { + LocaleInfo systemDefaultInfo = new LocaleInfo(""); + systemDefaultInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE; + systemDefaultInfo.mIsTranslated = true; + return systemDefaultInfo; + } + /* * Show all the languages supported for a country in the suggested list. * This is also handy for devices without SIM (tablets). diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS index e6c911e5b41d..3833976fcdb1 100644 --- a/core/java/com/android/internal/app/OWNERS +++ b/core/java/com/android/internal/app/OWNERS @@ -9,3 +9,6 @@ per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS per-file *Assist* = file:/core/java/android/service/voice/OWNERS per-file *Hotword* = file:/core/java/android/service/voice/OWNERS per-file *Voice* = file:/core/java/android/service/voice/OWNERS + +# System language settings +per-file *Locale* = file:platform/packages/apps/Settings:/src/com/android/settings/localepicker/OWNERS
\ No newline at end of file diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 46f47a31441c..c3e7920ffc08 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -53,6 +53,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { private static final int TYPE_HEADER_SUGGESTED = 0; private static final int TYPE_HEADER_ALL_OTHERS = 1; private static final int TYPE_LOCALE = 2; + private static final int TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER = 3; private static final int MIN_REGIONS_FOR_SUGGESTIONS = 6; private ArrayList<LocaleStore.LocaleInfo> mLocaleOptions; @@ -64,10 +65,18 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { private Locale mDisplayLocale = null; // used to potentially cache a modified Context that uses mDisplayLocale private Context mContextOverride = null; + private String mAppPackageName; public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) { + this(localeOptions, countryMode, null); + } + + public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode, + String appPackageName) { mCountryMode = countryMode; mLocaleOptions = new ArrayList<>(localeOptions.size()); + mAppPackageName = appPackageName; + for (LocaleStore.LocaleInfo li : localeOptions) { if (li.isSuggested()) { mSuggestionCount++; @@ -83,7 +92,8 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { @Override public boolean isEnabled(int position) { - return getItemViewType(position) == TYPE_LOCALE; + return getItemViewType(position) == TYPE_LOCALE + || getItemViewType(position) == TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER; } @Override @@ -97,13 +107,20 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { if (position == mSuggestionCount + 1) { return TYPE_HEADER_ALL_OTHERS; } + + LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position); + if (item.isSystemLocale()) { + return TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER; + } return TYPE_LOCALE; } } @Override public int getViewTypeCount() { - if (showHeaders()) { + if (!TextUtils.isEmpty(mAppPackageName) && showHeaders()) { + return 4; // Two headers and 1 for "System language" + } else if (showHeaders()) { return 3; // Two headers in addition to the locales } else { return 1; // Locales items only @@ -187,6 +204,21 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { textView.setTextLocale( mDisplayLocale != null ? mDisplayLocale : Locale.getDefault()); break; + + case TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER: + if (!(convertView instanceof ViewGroup)) { + convertView = mInflater.inflate( + R.layout.app_language_picker_system_default, parent, false); + } + + Locale defaultLocale = Locale.getDefault(); + TextView title = convertView.findViewById(R.id.locale); + title.setText(R.string.system_locale_title); + title.setTextLocale(defaultLocale); + TextView subtitle = convertView.findViewById(R.id.system_locale_subtitle); + subtitle.setText(defaultLocale.getDisplayName()); + subtitle.setTextLocale(defaultLocale); + break; default: // Covers both null, and "reusing" a wrong kind of view if (!(convertView instanceof ViewGroup)) { @@ -316,4 +348,8 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { public Filter getFilter() { return new FilterByNativeAndUiNames(); } + + public String getAppPackageName() { + return mAppPackageName; + } } diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index b18c98b58516..2ee47b64b1a5 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -43,5 +43,5 @@ oneway interface IInputMethodPrivilegedOperations { void notifyUserActionAsync(); void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible); void onStylusHandwritingReady(int requestId, int pid); - void finishStylusHandwriting(int requestId); + void resetStylusHandwriting(int requestId); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index e8a2d810d563..15d7acfb6e0a 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -416,13 +416,13 @@ public final class InputMethodPrivilegedOperations { * @param requestId */ @AnyThread - public void finishStylusHandwriting(int requestId) { + public void resetStylusHandwriting(int requestId) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; } try { - ops.finishStylusHandwriting(requestId); + ops.resetStylusHandwriting(requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 74749cc9bf0b..fd8534d45b2b 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -29,6 +29,8 @@ import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -60,6 +62,7 @@ import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; import com.android.internal.R; +import com.android.internal.protolog.common.ProtoLog; import java.util.List; @@ -98,10 +101,6 @@ public class TransitionAnimation { private static final String DEFAULT_PACKAGE = "android"; - // TODO (b/215515255): remove once we full migrate to shell transitions - private static final boolean SHELL_TRANSITIONS_ENABLED = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); - private final Context mContext; private final String mTag; @@ -256,9 +255,6 @@ public class TransitionAnimation { resId = ent.array.getResourceId(animAttr, 0); } } - if (!SHELL_TRANSITIONS_ENABLED) { - resId = updateToLegacyIfNeeded(resId); - } resId = updateToTranslucentAnimIfNeeded(resId, transit); if (ResourceId.isValid(resId)) { return loadAnimationSafely(context, resId, mTag); @@ -266,24 +262,6 @@ public class TransitionAnimation { return null; } - /** - * Replace animations that are not compatible with the legacy transition system with ones that - * are compatible with it. - * TODO (b/215515255): remove once we full migrate to shell transitions - */ - private int updateToLegacyIfNeeded(int anim) { - if (anim == R.anim.activity_open_enter) { - return R.anim.activity_open_enter_legacy; - } else if (anim == R.anim.activity_open_exit) { - return R.anim.activity_open_exit_legacy; - } else if (anim == R.anim.activity_close_enter) { - return R.anim.activity_close_enter_legacy; - } else if (anim == R.anim.activity_close_exit) { - return R.anim.activity_close_exit_legacy; - } - return anim; - } - /** Load animation by attribute Id from a specific AnimationStyle resource. */ @Nullable public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr, @@ -320,9 +298,9 @@ public class TransitionAnimation { @Nullable private AttributeCache.Entry getCachedAnimations(LayoutParams lp) { if (mDebug) { - Slog.v(mTag, "Loading animations: layout params pkg=" - + (lp != null ? lp.packageName : null) - + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); + ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: layout params pkg=%s resId=0x%x", + lp != null ? lp.packageName : null, + lp != null ? lp.windowAnimations : 0); } if (lp != null && lp.windowAnimations != 0) { // If this is a system resource, don't try to load it from the @@ -334,7 +312,7 @@ public class TransitionAnimation { packageName = DEFAULT_PACKAGE; } if (mDebug) { - Slog.v(mTag, "Loading animations: picked package=" + packageName); + ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: picked package=%s", packageName); } return AttributeCache.instance().get(packageName, resId, com.android.internal.R.styleable.WindowAnimation); @@ -345,16 +323,16 @@ public class TransitionAnimation { @Nullable private AttributeCache.Entry getCachedAnimations(String packageName, int resId) { if (mDebug) { - Slog.v(mTag, "Loading animations: package=" - + packageName + " resId=0x" + Integer.toHexString(resId)); + ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: package=%s resId=0x%x", + packageName, resId); } if (packageName != null) { if ((resId & 0xFF000000) == 0x01000000) { packageName = DEFAULT_PACKAGE; } if (mDebug) { - Slog.v(mTag, "Loading animations: picked package=" - + packageName); + ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: picked package=%s", + packageName); } return AttributeCache.instance().get(packageName, resId, com.android.internal.R.styleable.WindowAnimation); diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 45c6d5f10a5a..7f36c79591b3 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -60,6 +60,7 @@ public enum ProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM), WM_DEBUG_APP_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_ANIM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), WM_DEBUG_APP_TRANSITIONS_ANIM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), WM_DEBUG_RECENTS_ANIMATIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 273c5f170812..40d89db6165c 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -67,4 +67,6 @@ oneway interface IInputMethod { in List<MotionEvent> events); void initInkWindow(); + + void finishStylusHandwriting(); } diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java index 361ba958f759..1074004b4c33 100644 --- a/core/java/com/android/internal/widget/LockscreenCredential.java +++ b/core/java/com/android/internal/widget/LockscreenCredential.java @@ -175,27 +175,6 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { return mCredential; } - /** - * Returns the credential type recognized by {@link StorageManager}. Can be one of - * {@link StorageManager#CRYPT_TYPE_DEFAULT}, {@link StorageManager#CRYPT_TYPE_PATTERN}, - * {@link StorageManager#CRYPT_TYPE_PIN} or {@link StorageManager#CRYPT_TYPE_PASSWORD}. - */ - public int getStorageCryptType() { - if (isNone()) { - return StorageManager.CRYPT_TYPE_DEFAULT; - } - if (isPattern()) { - return StorageManager.CRYPT_TYPE_PATTERN; - } - if (isPin()) { - return StorageManager.CRYPT_TYPE_PIN; - } - if (isPassword()) { - return StorageManager.CRYPT_TYPE_PASSWORD; - } - throw new IllegalStateException("Unhandled credential type"); - } - /** Returns whether this is an empty credential */ public boolean isNone() { ensureNotZeroized(); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 45b6c786670d..4f9558885e40 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -471,11 +471,9 @@ android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" /> <!-- Defined in RestrictionsManager --> - <protected-broadcast - android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" /> - <!-- Defined in RestrictionsManager --> + <protected-broadcast android:name="android.content.action.PERMISSION_RESPONSE_RECEIVED" /> + <protected-broadcast android:name="android.content.action.REQUEST_PERMISSION" /> - <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" /> <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_STARTED" /> <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" /> <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_PROGRESS" /> diff --git a/core/res/res/anim/activity_close_enter_legacy.xml b/core/res/res/anim/activity_close_enter_legacy.xml deleted file mode 100644 index 9fa7c5498ea6..000000000000 --- a/core/res/res/anim/activity_close_enter_legacy.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2009, 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. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <scale - android:fromXScale="1.1" - android:toXScale="1" - android:fromYScale="1.1" - android:toYScale="1" - android:pivotX="50%" - android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> -</set>
\ No newline at end of file diff --git a/core/res/res/anim/activity_close_exit_legacy.xml b/core/res/res/anim/activity_close_exit_legacy.xml deleted file mode 100644 index 1599ae8cb19f..000000000000 --- a/core/res/res/anim/activity_close_exit_legacy.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2009, 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. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false" - android:zAdjustment="top"> - <alpha - android:fromAlpha="1" - android:toAlpha="0.0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/linear" - android:startOffset="33" - android:duration="50"/> - <scale - android:fromXScale="1" - android:toXScale="0.9" - android:fromYScale="1" - android:toYScale="0.9" - android:pivotX="50%" - android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> -</set> diff --git a/core/res/res/anim/activity_open_enter_legacy.xml b/core/res/res/anim/activity_open_enter_legacy.xml deleted file mode 100644 index 38d3e8ed06ce..000000000000 --- a/core/res/res/anim/activity_open_enter_legacy.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- -/* -** Copyright 2009, 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. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha - android:fromAlpha="0" - android:toAlpha="1.0" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/linear" - android:startOffset="50" - android:duration="50"/> - <scale - android:fromXScale="0.85" - android:toXScale="1" - android:fromYScale="0.85" - android:toYScale="1" - android:pivotX="50%" - android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> -</set> diff --git a/core/res/res/anim/activity_open_exit_legacy.xml b/core/res/res/anim/activity_open_exit_legacy.xml deleted file mode 100644 index 3865d2149f42..000000000000 --- a/core/res/res/anim/activity_open_exit_legacy.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- -/* -** Copyright 2009, 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. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - - <!-- Fade out, over a black surface, which simulates a black scrim --> - <alpha - android:fromAlpha="1" - android:toAlpha="0.4" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/linear" - android:startOffset="83" - android:duration="167"/> - - <scale - android:fromXScale="1" - android:toXScale="1.05" - android:fromYScale="1" - android:toYScale="1.05" - android:pivotX="50%" - android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" - android:fillAfter="true" - android:interpolator="@interpolator/fast_out_extra_slow_in" - android:duration="400"/> -</set>
\ No newline at end of file diff --git a/core/res/res/layout/app_language_picker_system_default.xml b/core/res/res/layout/app_language_picker_system_default.xml new file mode 100644 index 000000000000..d9883bc3df04 --- /dev/null +++ b/core/res/res/layout/app_language_picker_system_default.xml @@ -0,0 +1,42 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:minHeight="?android:attr/listPreferredItemHeight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="vertical" + android:paddingTop="8dp"> + <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/locale" + android:gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceListItem" + android:textDirection="locale" + android:layoutDirection="locale" + android:layout_weight="1" /> + + <TextView + android:id="@+id/system_locale_subtitle" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" + android:layout_weight="1" /> +</LinearLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2107f651eade..2295090b6616 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2322,7 +2322,9 @@ <attr name="windowSplashScreenAnimatedIcon" format="reference"/> <!-- The duration, in milliseconds, of the window splash screen icon animation duration when playing the splash screen starting window. The maximum animation duration should - be limited below 1000ms. --> + be limited below 1000ms. + @deprecated Not used by framework starting from API level 33. The system estimates the + duration of the vector animation automatically. --> <attr name="windowSplashScreenAnimationDuration" format="integer"/> <!-- Place an drawable image in the bottom of the starting window, it can be used to diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0344a93571a5..94eb45cab8d6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2723,7 +2723,7 @@ <string name="config_bandwidthEstimateSource">bandwidth_estimator</string> <!-- Whether force to enable telephony new data stack or not --> - <bool name="config_force_enable_telephony_new_data_stack">false</bool> + <bool name="config_force_enable_telephony_new_data_stack">true</bool> <!-- Whether WiFi display is supported by this device. There are many prerequisites for this feature to work correctly. @@ -2931,8 +2931,8 @@ <!-- Apps that are authorized to access shared accounts, overridden by product overlays --> <string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string> - <!-- Settings intelligence package name --> - <string name="config_settingsIntelligencePackageName" translatable="false"> + <!-- System settings intelligence package name --> + <string name="config_systemSettingsIntelligence" translatable="false"> com.android.settings.intelligence </string> @@ -2963,6 +2963,11 @@ the currently focused view is a text editor. --> <bool name="config_preventImeStartupUnlessTextEditor">false</bool> + <!-- These IMEs are known not to behave well when evicted from memory and thus are exempt + from the IME startup prevention behavior that is enabled by + config_preventImeStartupUnlessTextEditor. --> + <string-array name="config_nonPreemptibleInputMethods" translatable="false" /> + <!-- The list of classes that should be added to the notification ranking pipeline. See {@link com.android.server.notification.NotificationSignalExtractor} If you add a new extractor to this list make sure to update @@ -5803,4 +5808,14 @@ because of it. --> <bool name="config_bg_current_drain_high_threshold_by_bg_location">false</bool> + + <!-- Start safety protection resources to be overlaid --> + + <!-- Safety protection icon to be overlaid --> + <item name="ic_safety_protection" type="drawable">@null</item> + + <!-- Display text for safety protection to be overlaid. This is translatable --> + <string name="safety_protection_display_text"></string> + + <!-- End safety protection resources to be overlaid --> </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index e35d2e9f488a..2997fef00f2f 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -174,6 +174,10 @@ <public name="config_systemAutomotiveCalendarSyncManager" /> <!-- @hide @SystemApi --> <public name="config_defaultAutomotiveNavigation" /> + <!-- @hide @SystemApi --> + <public name="safety_protection_display_text" /> + <!-- @hide @SystemApi --> + <public name="config_systemSettingsIntelligence" /> </staging-public-group> <staging-public-group type="dimen" first-id="0x01db0000"> @@ -188,6 +192,8 @@ </staging-public-group> <staging-public-group type="drawable" first-id="0x01d80000"> + <!-- @hide @SystemApi --> + <public name="ic_safety_protection" /> </staging-public-group> <staging-public-group type="layout" first-id="0x01d70000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 4c71b3abd00c..4a2c9119b745 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2988,9 +2988,6 @@ <!-- Displayed to the user to confirm that they have copied text from a web page to the clipboard. --> <string name="text_copied">Text copied to clipboard.</string> - <!-- Displayed to the user to confirm that they have copied text/images to the clipboard [CHAR LIMIT=NONE] --> - <string name="copied">Copied</string> - <!-- Displayed to the user to inform them that an app has accessed clipboard data (pasted as in "copy and paste") that was copied from another app [CHAR LIMIT=50] --> <string name="pasted_from_app"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted from <xliff:g id="source_app_name" example="Chrome">%2$s</xliff:g></string> @@ -6296,4 +6293,7 @@ ul.</string> <!-- Strings for VirtualDeviceManager --> <!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] --> <string name="vdm_camera_access_denied">Cannot access camera from this device</string> + + <!-- Title for preference of the system default locale. [CHAR LIMIT=50]--> + <string name="system_locale_title">System language</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1f0b22b9e460..845de8a3492e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1713,10 +1713,6 @@ <java-symbol type="anim" name="activity_open_exit" /> <java-symbol type="anim" name="activity_close_enter" /> <java-symbol type="anim" name="activity_close_exit" /> - <java-symbol type="anim" name="activity_open_enter_legacy" /> - <java-symbol type="anim" name="activity_open_exit_legacy" /> - <java-symbol type="anim" name="activity_close_enter_legacy" /> - <java-symbol type="anim" name="activity_close_exit_legacy" /> <java-symbol type="anim" name="task_fragment_close_enter" /> <java-symbol type="anim" name="task_fragment_close_exit" /> <java-symbol type="anim" name="task_fragment_open_enter" /> @@ -2269,6 +2265,7 @@ <java-symbol type="string" name="config_notificationAccessConfirmationActivity" /> <java-symbol type="bool" name="config_killableInputMethods" /> <java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" /> + <java-symbol type="array" name="config_nonPreemptibleInputMethods" /> <java-symbol type="layout" name="resolver_list" /> <java-symbol type="id" name="resolver_list" /> @@ -4649,8 +4646,6 @@ <java-symbol type="drawable" name="ic_accessibility_24dp" /> <java-symbol type="string" name="view_and_control_notification_title" /> <java-symbol type="string" name="view_and_control_notification_content" /> - <java-symbol type="array" name="config_accessibility_allowed_install_source" /> - <!-- Translation --> <java-symbol type="string" name="ui_translation_accessibility_translated_text" /> <java-symbol type="string" name="ui_translation_accessibility_translation_finished" /> @@ -4772,4 +4767,7 @@ <java-symbol type="drawable" name="ic_swap_horiz" /> <java-symbol type="bool" name="config_notificationForceUserSetOnUpgrade" /> + <java-symbol type="string" name="system_locale_title" /> + <java-symbol type="layout" name="app_language_picker_system_default" /> + <java-symbol type="id" name="system_locale_subtitle" /> </resources> diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java index cd61011c856a..863aac35c0f3 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java @@ -45,7 +45,13 @@ import org.mockito.MockitoAnnotations; public class BatteryStatsHistoryTest { private static final String TAG = "BatteryStatsHistoryTest"; private static final int MAX_HISTORY_FILES = 32; - private final BatteryStatsImpl mBatteryStatsImpl = new MockBatteryStatsImpl(); + private static final int MAX_HISTORY_BUFFER_KB = 128; + + // Initializing max history files and buffer to the default values of non-low-ram device + // to maintain consistency in the tests + private final BatteryStatsImpl mBatteryStatsImpl = new MockBatteryStatsImpl() + .setMaxHistoryFiles(MAX_HISTORY_FILES) + .setMaxHistoryBuffer(MAX_HISTORY_BUFFER_KB * 1024); private final Parcel mHistoryBuffer = Parcel.obtain(); private File mSystemDir; private File mHistoryDir; diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index 1bb41a8cfffd..00154a3d23b0 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -26,6 +26,7 @@ import android.net.NetworkStats; import android.os.Handler; import android.os.Looper; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; @@ -193,6 +194,18 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return this; } + @GuardedBy("this") + public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) { + mConstants.MAX_HISTORY_FILES = maxHistoryFiles; + return this; + } + + @GuardedBy("this") + public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) { + mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer; + return this; + } + public int getAndClearExternalStatsSyncFlags() { final int flags = mExternalStatsSync.flags; mExternalStatsSync.flags = 0; diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 4449c48d28bb..99cb40a7fce7 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -19,6 +19,12 @@ "group": "WM_DEBUG_LOCKTASK", "at": "com\/android\/server\/wm\/LockTaskController.java" }, + "-2111539867": { + "message": "remove IME snapshot, caller=%s", + "level": "INFO", + "group": "WM_DEBUG_IME", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-2109936758": { "message": "removeAppToken make exiting: %s", "level": "VERBOSE", @@ -61,6 +67,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/Task.java" }, + "-2052051397": { + "message": "Clear animatingExit: reason=destroySurface win=%s", + "level": "DEBUG", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-2049725903": { "message": "Task back pressed on root taskId=%d", "level": "VERBOSE", @@ -127,6 +139,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "-1969928125": { + "message": "Animation start for %s, anim=%s", + "level": "DEBUG", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/SurfaceAnimator.java" + }, "-1963461591": { "message": "Removing %s from %s", "level": "VERBOSE", @@ -163,6 +181,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1933723759": { + "message": "Clear animatingExit: reason=relayoutVisibleWindow win=%s", + "level": "DEBUG", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-1924376693": { "message": " Setting Ready-group to %b. group=%s from %s", "level": "VERBOSE", @@ -265,6 +289,12 @@ "group": "WM_DEBUG_RESIZE", "at": "com\/android\/server\/wm\/WindowState.java" }, + "-1814361639": { + "message": "Set IME snapshot position: (%d, %d)", + "level": "INFO", + "group": "WM_DEBUG_IME", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-1810446914": { "message": "Trying to update display configuration for system\/invalid process.", "level": "WARN", @@ -307,6 +337,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "-1777010776": { + "message": "create IME snapshot for %s, buff width=%s, height=%s", + "level": "INFO", + "group": "WM_DEBUG_IME", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-1770075711": { "message": "Adding window client %s that is dead, aborting.", "level": "WARN", @@ -613,6 +649,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1471518109": { + "message": "Set animatingExit: reason=onAppVisibilityChanged win=%s", + "level": "DEBUG", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-1468740466": { "message": "Moving to PAUSED: %s (starting in paused state)", "level": "VERBOSE", @@ -751,6 +793,12 @@ "group": "WM_DEBUG_CONTENT_RECORDING", "at": "com\/android\/server\/wm\/ContentRecorder.java" }, + "-1318478129": { + "message": "applyAnimation: win=%s anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowStateAnimator.java" + }, "-1311436264": { "message": "Unregister task fragment organizer=%s uid=%d pid=%d", "level": "VERBOSE", @@ -781,12 +829,24 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/ActivityStarter.java" }, + "-1303628829": { + "message": "**** STARTING EXIT", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/DisplayPolicy.java" + }, "-1292329638": { "message": "Added starting %s: startingWindow=%s startingView=%s", "level": "VERBOSE", "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1288007399": { + "message": "performShowLocked: mDrawState=HAS_DRAWN in %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-1270731689": { "message": "Attempted to set replacing window on app token with no content %s", "level": "WARN", @@ -835,6 +895,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimationController.java" }, + "-1209252064": { + "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s", + "level": "DEBUG", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-1207757583": { "message": "startAnimation(): Notify animation start: %s", "level": "DEBUG", @@ -1219,6 +1285,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-799003045": { + "message": "Set animatingExit: reason=remove\/replaceWindow win=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-784959154": { "message": "Attempted to add private presentation window to a non-private display. Aborting.", "level": "WARN", @@ -1375,6 +1447,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "-658964693": { + "message": "onWindowAnimationFinished, wc=%s, type=%s, imeSnapshot=%s, target=%s", + "level": "INFO", + "group": "WM_DEBUG_IME", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-655104359": { "message": "Frontmost changed immersion: %s", "level": "DEBUG", @@ -1675,6 +1753,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-347866078": { + "message": "Setting move animation on %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-344488673": { "message": "Finishing drawing window %s: mDrawState=%s", "level": "VERBOSE", @@ -1699,6 +1783,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowState.java" }, + "-319689203": { + "message": "Reparenting to original parent: %s for %s", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/SurfaceAnimator.java" + }, "-317761482": { "message": "Create sleep token: tag=%s, displayId=%d", "level": "DEBUG", @@ -1807,6 +1897,18 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/WindowState.java" }, + "-208664771": { + "message": "Reparenting to leash for %s", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/SurfaceAnimator.java" + }, + "-203358733": { + "message": "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowStateAnimator.java" + }, "-198463978": { "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b", "level": "VERBOSE", @@ -1903,6 +2005,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "-91393839": { + "message": "Set animatingExit: reason=remove\/applyAnimation win=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-90559682": { "message": "Config is skipping already pausing %s", "level": "VERBOSE", @@ -1927,6 +2035,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/Session.java" }, + "-81121442": { + "message": "ImeContainer just became organized but it doesn't have a parent or the parent doesn't have a surface control. mSurfaceControl=%s imeParentSurfaceControl=%s", + "level": "ERROR", + "group": "WM_DEBUG_IME", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-80004683": { "message": "Resume failed; resetting state to %s: %s", "level": "VERBOSE", @@ -1939,6 +2053,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" }, + "-57750640": { + "message": "show IME snapshot, ime target=%s, callers=%s", + "level": "INFO", + "group": "WM_DEBUG_IME", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-55185509": { "message": "setFocusedTask: taskId=%d touchedActivity=%s", "level": "DEBUG", @@ -1957,6 +2077,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, + "-32102932": { + "message": "Error sending initial configuration change to WindowContainer overlay", + "level": "ERROR", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "-23020844": { "message": "Back: Reset surfaces", "level": "DEBUG", @@ -2191,6 +2317,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/Task.java" }, + "215077284": { + "message": "Animation start delayed for %s", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/SurfaceAnimator.java" + }, "221540118": { "message": "mUserActivityTimeout set to %d", "level": "DEBUG", @@ -2281,6 +2413,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransition.java" }, + "283489582": { + "message": "Clear animatingExit: reason=exitAnimationDone win=%s", + "level": "DEBUG", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "288485303": { "message": "Attempted to set remove mode to a display that does not exist: %d", "level": "WARN", @@ -2353,6 +2491,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "341360111": { + "message": "selectAnimation in %s: transit=%d", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/DisplayPolicy.java" + }, "342460966": { "message": "DRAG %s: pos=(%d,%d)", "level": "INFO", @@ -2419,6 +2563,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "385595355": { + "message": "Starting animation on %s: type=%d, anim=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "397105698": { "message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found", "level": "VERBOSE", @@ -2431,6 +2581,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "397862437": { + "message": "Cancelling animation restarting=%b for %s", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/SurfaceAnimator.java" + }, "399841913": { "message": "SURFACE RECOVER DESTROY: %s", "level": "INFO", @@ -2767,12 +2923,6 @@ "group": "WM_DEBUG_WALLPAPER", "at": "com\/android\/server\/wm\/WallpaperWindowToken.java" }, - "736003885": { - "message": "Unable to retrieve the task token to start recording for display %d", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/ContentRecorder.java" - }, "736692676": { "message": "Config is relaunching %s", "level": "VERBOSE", @@ -2803,6 +2953,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "769218938": { + "message": "Loaded animation %s for %s, duration: %d, stack=%s", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "778774915": { "message": "Unable to record task since feature is disabled %d", "level": "VERBOSE", @@ -2959,6 +3115,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "975275467": { + "message": "Set animatingExit: reason=remove\/isAnimating win=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "979347997": { "message": "Launch on display check: disallow activity embedding without permission.", "level": "DEBUG", @@ -3127,6 +3289,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" }, + "1164325516": { + "message": "onExitAnimationDone in %s: exiting=%b remove=%b selfAnimating=%b anim=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "1166381079": { "message": "Execute app transition: %s, displayId: %d Callers=%s", "level": "WARN", @@ -3139,6 +3307,12 @@ "group": "WM_DEBUG_BACK_PREVIEW", "at": "com\/android\/server\/wm\/BackNavigationController.java" }, + "1175495463": { + "message": "ImeContainer just became organized. Reparenting under parent. imeParentSurfaceControl=%s", + "level": "INFO", + "group": "WM_DEBUG_IME", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "1178653181": { "message": "Old wallpaper still the target.", "level": "VERBOSE", @@ -3265,6 +3439,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/TransitionController.java" }, + "1335791109": { + "message": "createSurface %s: mDrawState=DRAW_PENDING", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowStateAnimator.java" + }, "1337596507": { "message": "Sending to proc %s new compat %s", "level": "VERBOSE", @@ -3679,6 +3859,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1810209625": { + "message": "Animation done in %s: exiting=%b, reportedVisible=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowStateAnimator.java" + }, "1822314934": { "message": "Expected target rootTask=%s to restored behind rootTask=%s but it is behind rootTask=%s", "level": "WARN", @@ -3757,6 +3943,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java" }, + "1878927091": { + "message": "prepareSurface: No changes in animation for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowStateAnimator.java" + }, "1891501279": { "message": "cancelAnimation(): reason=%s", "level": "DEBUG", @@ -3859,6 +4051,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/InputMonitor.java" }, + "2010476671": { + "message": "Animation done in %s: reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "2018454757": { "message": "WS.removeImmediately: %s Already removed...", "level": "VERBOSE", @@ -3871,6 +4069,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "2019765997": { + "message": "selectRotationAnimation topFullscreen=%s rotationAnimation=%d forceJumpcut=%b", + "level": "INFO", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/DisplayRotation.java" + }, "2022422429": { "message": "createAnimationAdapter(): container=%s", "level": "DEBUG", @@ -3925,6 +4129,12 @@ "group": "WM_DEBUG_WINDOW_INSETS", "at": "com\/android\/server\/wm\/InsetsSourceProvider.java" }, + "2075693141": { + "message": "Set animatingExit: reason=startExitingAnimation\/%s win=%s", + "level": "DEBUG", + "group": "WM_DEBUG_ANIM", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "2083556954": { "message": "Set mOrientationChanging of %s", "level": "VERBOSE", @@ -3984,6 +4194,9 @@ "WM_DEBUG_ADD_REMOVE": { "tag": "WindowManager" }, + "WM_DEBUG_ANIM": { + "tag": "WindowManager" + }, "WM_DEBUG_APP_TRANSITIONS": { "tag": "WindowManager" }, diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index c1addbf43937..bfa6ce5d36c5 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -1000,7 +1000,7 @@ public class RippleDrawable extends LayerDrawable { } private int clampAlpha(@ColorInt int color) { - if (Color.alpha(color) > 128) { + if (Color.alpha(color) < 128) { return (color & 0x00FFFFFF) | 0x80000000; } return color; 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 2aa695346c89..418ff0e7263a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -37,6 +37,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; @@ -58,14 +59,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Currently applied split configuration. private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); - private final List<TaskFragmentContainer> mContainers = new ArrayList<>(); - private final List<SplitContainer> mSplitContainers = new ArrayList<>(); + /** + * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info + * below it. + * When the app is host of multiple Tasks, there can be multiple splits controlled by the same + * organizer. + */ + private final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); // Callback to Jetpack to notify about changes to split states. private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); // We currently only support split activity embedding within the one root Task. + // TODO(b/207720388): move to TaskContainer private final Rect mParentBounds = new Rect(); public SplitController() { @@ -244,7 +251,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken()); } else { // Put activity into a new expanded container - final TaskFragmentContainer newContainer = newContainer(launchedActivity); + final TaskFragmentContainer newContainer = newContainer(launchedActivity, + launchedActivity.getTaskId()); mPresenter.expandActivity(newContainer.getTaskFragmentToken(), launchedActivity); } @@ -327,12 +335,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @Nullable TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { - for (TaskFragmentContainer container : mContainers) { - if (container.hasActivity(activityToken)) { - return container; + for (int i = mTaskContainers.size() - 1; i >= 0; i--) { + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + for (TaskFragmentContainer container : containers) { + if (container.hasActivity(activityToken)) { + return container; + } } } - return null; } @@ -340,9 +350,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Creates and registers a new organized container with an optional activity that will be * re-parented to it in a WCT. */ - TaskFragmentContainer newContainer(@Nullable Activity activity) { - TaskFragmentContainer container = new TaskFragmentContainer(activity); - mContainers.add(container); + TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) { + final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId); + if (!mTaskContainers.contains(taskId)) { + mTaskContainers.put(taskId, new TaskContainer()); + } + mTaskContainers.get(taskId).mContainers.add(container); return container; } @@ -354,13 +367,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) { - SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, + final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, secondaryContainer, splitRule); // Remove container later to prevent pinning escaping toast showing in lock task mode. if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); } - mSplitContainers.add(splitContainer); + mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer); } /** @@ -368,15 +381,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ void removeContainer(@NonNull TaskFragmentContainer container) { // Remove all split containers that included this one - mContainers.remove(container); - List<SplitContainer> containersToRemove = new ArrayList<>(); - for (SplitContainer splitContainer : mSplitContainers) { + final int taskId = container.getTaskId(); + final TaskContainer taskContainer = mTaskContainers.get(taskId); + if (taskContainer == null) { + return; + } + taskContainer.mContainers.remove(container); + if (taskContainer.mContainers.isEmpty()) { + mTaskContainers.remove(taskId); + // No more TaskFragment in this Task, so no need to check split container. + return; + } + + final List<SplitContainer> containersToRemove = new ArrayList<>(); + for (SplitContainer splitContainer : taskContainer.mSplitContainers) { if (container.equals(splitContainer.getSecondaryContainer()) || container.equals(splitContainer.getPrimaryContainer())) { containersToRemove.add(splitContainer); } } - mSplitContainers.removeAll(containersToRemove); + taskContainer.mSplitContainers.removeAll(containersToRemove); } /** @@ -399,12 +423,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** - * Returns the topmost not finished container. + * Returns the topmost not finished container in Task of given task id. */ @Nullable - TaskFragmentContainer getTopActiveContainer() { - for (int i = mContainers.size() - 1; i >= 0; i--) { - TaskFragmentContainer container = mContainers.get(i); + TaskFragmentContainer getTopActiveContainer(int taskId) { + final TaskContainer taskContainer = mTaskContainers.get(taskId); + if (taskContainer == null) { + return null; + } + for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) { + final TaskFragmentContainer container = taskContainer.mContainers.get(i); if (!container.isFinished() && container.getRunningActivityCount() > 0) { return container; } @@ -434,7 +462,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (splitContainer == null) { return; } - if (splitContainer != mSplitContainers.get(mSplitContainers.size() - 1)) { + final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId()) + .mSplitContainers; + if (splitContainers == null + || splitContainer != splitContainers.get(splitContainers.size() - 1)) { // Skip position update - it isn't the topmost split. return; } @@ -455,8 +486,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @Nullable private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) { - for (int i = mSplitContainers.size() - 1; i >= 0; i--) { - SplitContainer splitContainer = mSplitContainers.get(i); + final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId()) + .mSplitContainers; + if (splitContainers == null) { + return null; + } + for (int i = splitContainers.size() - 1; i >= 0; i--) { + final SplitContainer splitContainer = splitContainers.get(i); if (container.equals(splitContainer.getSecondaryContainer()) || container.equals(splitContainer.getPrimaryContainer())) { return splitContainer; @@ -473,8 +509,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private SplitContainer getActiveSplitForContainers( @NonNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer) { - for (int i = mSplitContainers.size() - 1; i >= 0; i--) { - SplitContainer splitContainer = mSplitContainers.get(i); + final List<SplitContainer> splitContainers = mTaskContainers.get(firstContainer.getTaskId()) + .mSplitContainers; + if (splitContainers == null) { + return null; + } + for (int i = splitContainers.size() - 1; i >= 0; i--) { + final SplitContainer splitContainer = splitContainers.get(i); final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer(); if ((firstContainer == secondary && secondContainer == primary) @@ -501,7 +542,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer container = getContainerWithActivity( activity.getActivityToken()); // Don't launch placeholder if the container is occluded. - if (container != null && container != getTopActiveContainer()) { + if (container != null && container != getTopActiveContainer(container.getTaskId())) { return false; } @@ -588,24 +629,30 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Nullable private List<SplitInfo> getActiveSplitStates() { List<SplitInfo> splitStates = new ArrayList<>(); - for (SplitContainer container : mSplitContainers) { - if (container.getPrimaryContainer().isEmpty() - || container.getSecondaryContainer().isEmpty()) { - // We are in an intermediate state because either the split container is about to be - // removed or the primary or secondary container are about to receive an activity. - return null; + for (int i = mTaskContainers.size() - 1; i >= 0; i--) { + final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i) + .mSplitContainers; + for (SplitContainer container : splitContainers) { + if (container.getPrimaryContainer().isEmpty() + || container.getSecondaryContainer().isEmpty()) { + // We are in an intermediate state because either the split container is about + // to be removed or the primary or secondary container are about to receive an + // activity. + return null; + } + final ActivityStack primaryContainer = container.getPrimaryContainer() + .toActivityStack(); + final ActivityStack secondaryContainer = container.getSecondaryContainer() + .toActivityStack(); + final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer, + // Splits that are not showing side-by-side are reported as having 0 split + // ratio, since by definition in the API the primary container occupies no + // width of the split when covered by the secondary. + mPresenter.shouldShowSideBySide(container) + ? container.getSplitRule().getSplitRatio() + : 0.0f); + splitStates.add(splitState); } - ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack(); - ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack(); - SplitInfo splitState = new SplitInfo(primaryContainer, - secondaryContainer, - // Splits that are not showing side-by-side are reported as having 0 split - // ratio, since by definition in the API the primary container occupies no - // width of the split when covered by the secondary. - mPresenter.shouldShowSideBySide(container) - ? container.getSplitRule().getSplitRatio() - : 0.0f); - splitStates.add(splitState); } return splitStates; } @@ -615,11 +662,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * the client. */ private boolean allActivitiesCreated() { - for (TaskFragmentContainer container : mContainers) { - if (container.getInfo() == null - || container.getInfo().getActivities().size() - != container.collectActivities().size()) { - return false; + 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()) { + return false; + } } } return true; @@ -633,7 +683,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container == null) { return false; } - for (SplitContainer splitContainer : mSplitContainers) { + final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId()) + .mSplitContainers; + if (splitContainers == null) { + return true; + } + for (SplitContainer splitContainer : splitContainers) { if (container.equals(splitContainer.getPrimaryContainer()) || container.equals(splitContainer.getSecondaryContainer())) { return false; @@ -684,9 +739,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Nullable TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { - for (TaskFragmentContainer container : mContainers) { - if (container.getTaskFragmentToken().equals(fragmentToken)) { - return container; + for (int i = mTaskContainers.size() - 1; i >= 0; i--) { + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + for (TaskFragmentContainer container : containers) { + if (container.getTaskFragmentToken().equals(fragmentToken)) { + return container; + } } } return null; @@ -969,4 +1027,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Not reuse if it needs to destroy the existing. return !pairRule.shouldClearTop(); } + + /** Represents TaskFragments and split pairs below a Task. */ + private static class TaskContainer { + final List<TaskFragmentContainer> mContainers = new ArrayList<>(); + final List<SplitContainer> mSplitContainers = new ArrayList<>(); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index ade573132eef..e7552ff48d52 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -80,7 +80,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container.finish(shouldFinishDependent, this, wct, mController); - final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(); + final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer( + container.getTaskId()); if (newTopContainer != null) { mController.updateContainer(wct, newTopContainer); } @@ -103,7 +104,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryActivity, primaryRectBounds, null); // Create new empty task fragment - final TaskFragmentContainer secondaryContainer = mController.newContainer(null); + final TaskFragmentContainer secondaryContainer = mController.newContainer(null, + primaryContainer.getTaskId()); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, isLtr(primaryActivity, rule)); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), @@ -159,7 +161,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Creates a new expanded container. */ TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) { - final TaskFragmentContainer newContainer = mController.newContainer(null); + final TaskFragmentContainer newContainer = mController.newContainer(null, + launchingActivity.getTaskId()); final WindowContainerTransaction wct = new WindowContainerTransaction(); createTaskFragment(wct, newContainer.getTaskFragmentToken(), @@ -180,7 +183,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { TaskFragmentContainer container = mController.getContainerWithActivity( activity.getActivityToken()); if (container == null || container == containerToAvoid) { - container = mController.newContainer(activity); + container = mController.newContainer(activity, activity.getTaskId()); final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( @@ -222,10 +225,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity.getActivityToken()); if (primaryContainer == null) { - primaryContainer = mController.newContainer(launchingActivity); + primaryContainer = mController.newContainer(launchingActivity, + launchingActivity.getTaskId()); } - TaskFragmentContainer secondaryContainer = mController.newContainer(null); + TaskFragmentContainer secondaryContainer = mController.newContainer(null, + primaryContainer.getTaskId()); final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); 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 4d2d0551d828..e49af41d4eac 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -16,6 +16,8 @@ package androidx.window.extensions.embedding; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -41,6 +43,9 @@ class TaskFragmentContainer { @NonNull private final IBinder mToken; + /** Parent leaf Task id. */ + private final int mTaskId; + /** * Server-provided task fragment information. */ @@ -71,8 +76,12 @@ class TaskFragmentContainer { * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. */ - TaskFragmentContainer(@Nullable Activity activity) { + TaskFragmentContainer(@Nullable Activity activity, int taskId) { mToken = new Binder("TaskFragmentContainer"); + if (taskId == INVALID_TASK_ID) { + throw new IllegalArgumentException("Invalid Task id"); + } + mTaskId = taskId; if (activity != null) { addPendingAppearedActivity(activity); } @@ -275,6 +284,11 @@ class TaskFragmentContainer { } } + /** Gets the parent leaf Task id. */ + int getTaskId() { + return mTaskId; + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 13d12b3589c0..677c1c7d7759 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -2802,7 +2802,14 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setVisibility(View.INVISIBLE); mExpandedViewContainer.setAlpha(0f); mExpandedViewContainer.addView(bev); - bev.setManageClickListener((view) -> showManageMenu(!mShowingManage)); + + postDelayed(() -> { + // Set the Manage button click handler from postDelayed. This appears to resolve + // a race condition with adding the BubbleExpandedView view to the expanded view + // container. Due to the race condition the click handler sometimes is not set up + // correctly and is never called. + bev.setManageClickListener((view) -> showManageMenu(true /* show */)); + }, 0); if (!mIsExpansionAnimating) { mSurfaceSynchronizer.syncSurfaceAndRun(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java index cc3a3b2206f3..35f1038a6853 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java @@ -51,7 +51,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { /** * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox - * Education for specific packages and which user has seen the full dialog for any package. + * Education dialog. */ @VisibleForTesting static final String HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME = @@ -66,6 +66,13 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { private final Transitions mTransitions; + /** + * The id of the current user, to associate with a boolean in {@link + * #HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME}, indicating whether that user has already seen the + * Letterbox Education dialog. + */ + private final int mUserId; + // Remember the last reported state in case visibility changes due to keyguard or IME updates. private boolean mEligibleForLetterboxEducation; @@ -98,6 +105,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { mTransitions = transitions; mOnDismissCallback = onDismissCallback; mAnimationController = animationController; + mUserId = taskInfo.userId; mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation; mSharedPreferences = mContext.getSharedPreferences(HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME, Context.MODE_PRIVATE); @@ -133,7 +141,6 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { @Override protected View createLayout() { - setSeenLetterboxEducation(); mLayout = inflateLayout(); updateDialogMargins(); @@ -177,6 +184,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { // Dialog has already been released. return; } + setSeenLetterboxEducation(); mLayout.setDismissOnClickListener(this::onDismiss); // Focus on the dialog title for accessibility. mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); @@ -241,7 +249,7 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract { } private String getPrefKey() { - return String.valueOf(mContext.getUserId()); + return String.valueOf(mUserId); } @VisibleForTesting 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 917eaa061d1b..46b8e6098273 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 @@ -234,11 +234,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } setState(STATE_PIP_MENU); + mTvPipMenuController.showMenu(); updatePinnedStackBounds(); } @Override - public void closeMenu() { + public void onMenuClosed() { if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: closeMenu(), state before=%s", TAG, stateToName(mState)); @@ -285,6 +286,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } @Override + public void enterPipMovementMenu() { + setState(STATE_PIP_MENU); + mTvPipMenuController.showMovementMenuOnly(); + } + + @Override public void movePip(int keycode) { if (mTvPipBoundsAlgorithm.updateGravity(keycode)) { mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity()); @@ -438,7 +445,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } mPipNotificationController.dismiss(); - mTvPipMenuController.hideMenu(); + mTvPipMenuController.closeMenu(); mTvPipBoundsState.resetTvPipState(); setState(STATE_NO_PIP); mPinnedTaskId = NONEXISTENT_TASK_ID; @@ -478,16 +485,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal TAG, stateToName(state), stateToName(mState)); } mState = state; - - if (mState == STATE_PIP_MENU) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: > show menu", TAG); - } - mTvPipMenuController.showMenu(); - } - - updatePinnedStackBounds(); } private void loadConfigurations() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index b6ae398c1eb9..35c34ac8315f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -65,6 +65,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis // User can actively move the PiP via the DPAD. private boolean mInMoveMode; + // Used when only showing the move menu since we want to close the menu completely when + // exiting the move menu instead of showing the regular button menu. + private boolean mCloseAfterExitMoveMenu; private final List<RemoteAction> mMediaActions = new ArrayList<>(); private final List<RemoteAction> mAppActions = new ArrayList<>(); @@ -102,7 +105,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis final BroadcastReceiver closeSystemDialogsBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - hideMenu(); + closeMenu(); } }; context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver, @@ -155,29 +158,49 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis 0, SHELL_ROOT_LAYER_PIP); } + void showMovementMenuOnly() { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: showMovementMenuOnly()", TAG); + } + mInMoveMode = true; + mCloseAfterExitMoveMenu = true; + showMenuInternal(); + } + @Override public void showMenu() { if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG); } + mInMoveMode = false; + mCloseAfterExitMoveMenu = false; + showMenuInternal(); + } - if (mPipMenuView != null) { - Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds()); - mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams( - MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height())); - maybeUpdateMenuViewActions(); - updateExpansionState(); - - SurfaceControl menuSurfaceControl = getSurfaceControl(); - if (menuSurfaceControl != null) { - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1); - t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top); - t.apply(); - } - grantPipMenuFocus(true); - mPipMenuView.show(mInMoveMode, mDelegate.getPipGravity()); + private void showMenuInternal() { + if (mPipMenuView == null) { + return; + } + Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds()); + mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams( + MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height())); + maybeUpdateMenuViewActions(); + updateExpansionState(); + + SurfaceControl menuSurfaceControl = getSurfaceControl(); + if (menuSurfaceControl != null) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1); + t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top); + t.apply(); + } + grantPipMenuFocus(true); + if (mInMoveMode) { + mPipMenuView.showMoveMenu(mDelegate.getPipGravity()); + } else { + mPipMenuView.showButtonMenu(); } } @@ -199,25 +222,18 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return menuBounds; } - void hideMenu() { - if (!isMenuVisible()) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: hideMenu() - Menu isn't visible, so don't hide", TAG); - } + void closeMenu() { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: closeMenu()", TAG); + } + if (mPipMenuView == null) { return; - } else { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: hideMenu()", TAG); - } } - mPipMenuView.hide(); - if (!mInMoveMode) { - grantPipMenuFocus(false); - mDelegate.closeMenu(); - } + mPipMenuView.hideAll(); + grantPipMenuFocus(false); + mDelegate.onMenuClosed(); } boolean isInMoveMode() { @@ -228,25 +244,29 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis public void onEnterMoveMode() { if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onEnterMoveMode - %b", TAG, mInMoveMode); + "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode, + mCloseAfterExitMoveMenu); } mInMoveMode = true; - mPipMenuView.showMenuButtons(false); - mPipMenuView.showMovementHints(mDelegate.getPipGravity()); - mDelegate.onInMoveModeChanged(); + mPipMenuView.showMoveMenu(mDelegate.getPipGravity()); } @Override public boolean onExitMoveMode() { if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: onExitMoveMode - %b", TAG, mInMoveMode); + "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode, + mCloseAfterExitMoveMenu); + } + if (mCloseAfterExitMoveMenu) { + mInMoveMode = false; + mCloseAfterExitMoveMenu = false; + closeMenu(); + return true; } if (mInMoveMode) { mInMoveMode = false; - mPipMenuView.showMenuButtons(true); - mPipMenuView.hideMovementHints(); - mDelegate.onInMoveModeChanged(); + mPipMenuView.showButtonMenu(); return true; } return false; @@ -266,7 +286,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void detach() { - hideMenu(); + closeMenu(); detachPipMenuView(); mLeash = null; } @@ -486,7 +506,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void onBackPress() { if (!onExitMoveMode()) { - hideMenu(); + closeMenu(); } } @@ -516,7 +536,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis void togglePipExpansion(); - void closeMenu(); + void onMenuClosed(); void closePip(); } 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 7fdb9ed90875..ccd054aa7680 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 @@ -177,29 +177,43 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { expanded ? R.string.pip_collapse : R.string.pip_expand); } - void show(boolean inMoveMode, int gravity) { + /** + * @param gravity for the arrow hints + */ + void showMoveMenu(int gravity) { if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: show(), inMoveMode: %b", TAG, inMoveMode); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG); } - if (inMoveMode) { - showMovementHints(gravity); - } else { - animateAlphaTo(1, mActionButtonsContainer); + showMenuButtons(false); + showMovementHints(gravity); + showMenuFrame(true); + } + + void showButtonMenu() { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG); } - animateAlphaTo(1, mMenuFrameView); + showMenuButtons(true); + hideMovementHints(); + showMenuFrame(true); } - void hide() { + /** + * Hides all menu views, including the menu frame. + */ + void hideAll() { if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hide()", TAG); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG); } - animateAlphaTo(0, mActionButtonsContainer); - animateAlphaTo(0, mMenuFrameView); + showMenuButtons(false); hideMovementHints(); + showMenuFrame(false); } private void animateAlphaTo(float alpha, View view) { + if (view.getAlpha() == alpha) { + return; + } view.animate() .alpha(alpha) .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER) @@ -419,6 +433,10 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { animateAlphaTo(show ? 1 : 0, mActionButtonsContainer); } + private void showMenuFrame(boolean show) { + animateAlphaTo(show ? 1 : 0, mMenuFrameView); + } + interface Listener { void onBackPress(); @@ -426,7 +444,10 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { void onEnterMoveMode(); /** - * @return whether move mode was exited + * Called when a button for exiting move mode was pressed. + * + * @return true if the event was handled or false if the key event should be handled by the + * next receiver. */ boolean onExitMoveMode(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index 7bd3ce9c45b2..4033f030b702 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -56,6 +56,10 @@ public class TvPipNotificationController { "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU"; private static final String ACTION_CLOSE_PIP = "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP"; + private static final String ACTION_MOVE_PIP = + "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP"; + private static final String ACTION_TOGGLE_EXPANDED_PIP = + "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP"; private final Context mContext; private final PackageManager mPackageManager; @@ -222,6 +226,8 @@ public class TvPipNotificationController { mIntentFilter = new IntentFilter(); mIntentFilter.addAction(ACTION_CLOSE_PIP); mIntentFilter.addAction(ACTION_SHOW_PIP_MENU); + mIntentFilter.addAction(ACTION_MOVE_PIP); + mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP); } boolean mRegistered = false; @@ -252,6 +258,10 @@ public class TvPipNotificationController { mDelegate.showPictureInPictureMenu(); } else if (ACTION_CLOSE_PIP.equals(action)) { mDelegate.closePip(); + } else if (ACTION_MOVE_PIP.equals(action)) { + mDelegate.enterPipMovementMenu(); + } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) { + mDelegate.togglePipExpansion(); } } } @@ -259,5 +269,7 @@ public class TvPipNotificationController { interface Delegate { void showPictureInPictureMenu(); void closePip(); + void enterPipMovementMenu(); + void togglePipExpansion(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index f0fb69f43499..b6c8cffb9699 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -301,8 +301,6 @@ public class SplashscreenContentDrawer { Color.TRANSPARENT); attrs.mSplashScreenIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable( R.styleable.Window_windowSplashScreenAnimatedIcon), null); - attrs.mAnimationDuration = safeReturnAttrDefault((def) -> typedArray.getInt( - R.styleable.Window_windowSplashScreenAnimationDuration, def), 0); attrs.mBrandingImage = safeReturnAttrDefault((def) -> typedArray.getDrawable( R.styleable.Window_windowSplashScreenBrandingImage), null); attrs.mIconBgColor = safeReturnAttrDefault((def) -> typedArray.getColor( @@ -310,9 +308,8 @@ public class SplashscreenContentDrawer { Color.TRANSPARENT); typedArray.recycle(); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "getWindowAttrs: window attributes color: %s, replace icon: %b, avd duration: %d", - Integer.toHexString(attrs.mWindowBgColor), attrs.mSplashScreenIcon != null, - attrs.mAnimationDuration); + "getWindowAttrs: window attributes color: %s, replace icon: %b", + Integer.toHexString(attrs.mWindowBgColor), attrs.mSplashScreenIcon != null); } /** Creates the wrapper with system theme to avoid unexpected styles from app. */ @@ -327,7 +324,6 @@ public class SplashscreenContentDrawer { private Drawable mSplashScreenIcon = null; private Drawable mBrandingImage = null; private int mIconBgColor = Color.TRANSPARENT; - private int mAnimationDuration = 0; } /** @@ -401,16 +397,13 @@ public class SplashscreenContentDrawer { SplashScreenView build() { Drawable iconDrawable; - final long animationDuration; if (mSuggestType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { // empty or legacy splash screen case - animationDuration = 0; mFinalIconSize = 0; } else if (mTmpAttrs.mSplashScreenIcon != null) { // Using the windowSplashScreenAnimatedIcon attribute iconDrawable = mTmpAttrs.mSplashScreenIcon; - animationDuration = mTmpAttrs.mAnimationDuration; // There is no background below the icon, so scale the icon up if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT @@ -440,11 +433,9 @@ public class SplashscreenContentDrawer { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); createIconDrawable(new BitmapDrawable(bitmap), true); } - animationDuration = 0; } - return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration, - mUiThreadInitTask); + return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, mUiThreadInitTask); } private class ShapeIconFactory extends BaseIconFactory { @@ -460,7 +451,7 @@ public class SplashscreenContentDrawer { } else { mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable( mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize, - mFinalIconSize, mSplashscreenWorkerHandler, mSplashScreenExecutor); + mFinalIconSize, mSplashscreenWorkerHandler); } } @@ -520,7 +511,7 @@ public class SplashscreenContentDrawer { } private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable, - long animationDuration, Consumer<Runnable> uiThreadInitTask) { + Consumer<Runnable> uiThreadInitTask) { Drawable foreground = null; Drawable background = null; if (iconDrawable != null) { @@ -536,7 +527,6 @@ public class SplashscreenContentDrawer { .setIconSize(iconSize) .setIconBackground(background) .setCenterViewDrawable(foreground) - .setAnimationDurationMillis(animationDuration) .setUiThreadInitConsumer(uiThreadInitTask) .setAllowHandleSolidColor(mAllowHandleSolidColor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index fdd5a1578f41..5f52071bf950 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -44,7 +44,6 @@ import android.util.PathParser; import android.window.SplashScreenView; import com.android.internal.R; -import com.android.wm.shell.common.ShellExecutor; import java.util.function.LongConsumer; @@ -63,15 +62,14 @@ public class SplashscreenIconDrawableFactory { */ static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor, @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize, - Handler splashscreenWorkerHandler, ShellExecutor splashScreenExecutor) { + Handler splashscreenWorkerHandler) { Drawable foreground; Drawable background = null; boolean drawBackground = backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor; if (foregroundDrawable instanceof Animatable) { - foreground = new AnimatableIconAnimateListener(foregroundDrawable, - splashScreenExecutor); + foreground = new AnimatableIconAnimateListener(foregroundDrawable); } else if (foregroundDrawable instanceof AdaptiveIconDrawable) { // If the icon is Adaptive, we already use the icon background. drawBackground = false; @@ -274,12 +272,9 @@ public class SplashscreenIconDrawableFactory { private boolean mAnimationTriggered; private AnimatorListenerAdapter mJankMonitoringListener; private boolean mRunning; - private long mDuration; private LongConsumer mStartListener; - private final ShellExecutor mSplashScreenExecutor; - AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable, - ShellExecutor splashScreenExecutor) { + AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) { super(foregroundDrawable); Callback callback = new Callback() { @Override @@ -299,7 +294,6 @@ public class SplashscreenIconDrawableFactory { } }; mForegroundDrawable.setCallback(callback); - mSplashScreenExecutor = splashScreenExecutor; mAnimatableIcon = (Animatable) mForegroundDrawable; } @@ -309,9 +303,8 @@ public class SplashscreenIconDrawableFactory { } @Override - public void prepareAnimate(long duration, LongConsumer startListener) { + public void prepareAnimate(LongConsumer startListener) { stopAnimation(); - mDuration = duration; mStartListener = startListener; } @@ -328,11 +321,11 @@ public class SplashscreenIconDrawableFactory { mJankMonitoringListener.onAnimationCancel(null); } if (mStartListener != null) { - mStartListener.accept(mDuration); + mStartListener.accept(0); } return; } - long animDuration = mDuration; + long animDuration = 0; if (mAnimatableIcon instanceof AnimatedVectorDrawable && ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration() > 0) { animDuration = ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration(); @@ -341,9 +334,8 @@ public class SplashscreenIconDrawableFactory { animDuration = ((AnimationDrawable) mAnimatableIcon).getTotalDuration(); } mRunning = true; - mSplashScreenExecutor.executeDelayed(this::stopAnimation, animDuration); if (mStartListener != null) { - mStartListener.accept(Math.max(animDuration, 0)); + mStartListener.accept(animDuration); } } @@ -359,7 +351,6 @@ public class SplashscreenIconDrawableFactory { @Override public void stopAnimation() { if (mRunning) { - mSplashScreenExecutor.removeCallbacks(this::stopAnimation); onAnimationEnd(); mJankMonitoringListener = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 2aa63b3571ab..b0e44a15b0da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -434,6 +434,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // If available use the background color provided through AnimationOptions backgroundColorForTransition = info.getAnimationOptions().getBackgroundColor(); + } else if (a.getBackgroundColor() != 0) { + // Otherwise fallback on the background color provided through the animation + // definition. + backgroundColorForTransition = a.getBackgroundColor(); } else if (change.getBackgroundColor() != 0) { // Otherwise default to the window's background color if provided through // the theme as the background color for the animation - the top most window diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 4607d8acc63e..596100dcdead 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -143,9 +143,7 @@ public class CompatUIControllerTest extends ShellTestCase { // Verify that the compat controls and letterbox education are updated with new size compat // info. - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); - clearInvocations(mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); @@ -156,9 +154,7 @@ public class CompatUIControllerTest extends ShellTestCase { true); // Verify that compat controls and letterbox education are removed with null task listener. - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); - clearInvocations(mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), /* taskListener= */ null); @@ -181,9 +177,7 @@ public class CompatUIControllerTest extends ShellTestCase { eq(mMockTaskListener)); // Verify that the layout is created again. - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); - clearInvocations(mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); @@ -206,9 +200,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); - clearInvocations(mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ @@ -217,9 +209,7 @@ public class CompatUIControllerTest extends ShellTestCase { true); // Verify that the layout is created again. - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); - clearInvocations(mController); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); mController.onCompatInfoChanged(taskInfo, mMockTaskListener); verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); @@ -294,8 +284,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout); // No update if the insets state is the same. - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState)); verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout); verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout); @@ -368,8 +357,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); // Verify button remains hidden after keyguard becomes not showing since IME is showing. mController.onKeyguardShowingChanged(false); @@ -395,8 +383,7 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mMockCompatLayout, times(2)).updateVisibility(false); verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false); - clearInvocations(mMockCompatLayout); - clearInvocations(mMockLetterboxEduLayout); + clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout); // Verify button remains hidden after IME is hidden since keyguard is showing. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java index 7d51b521a9fb..f3a8cf45b7f8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -75,6 +76,12 @@ import org.mockito.MockitoAnnotations; @SmallTest public class LetterboxEduWindowManagerTest extends ShellTestCase { + private static final int USER_ID_1 = 1; + private static final int USER_ID_2 = 2; + + private static final String PREF_KEY_1 = String.valueOf(USER_ID_1); + private static final String PREF_KEY_2 = String.valueOf(USER_ID_2); + private static final int TASK_ID = 1; private static final int TASK_WIDTH = 200; @@ -98,9 +105,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Mock private Runnable mOnDismissCallback; private SharedPreferences mSharedPreferences; - private String mPrefKey; @Nullable - private Boolean mInitialPrefValue = null; + private Boolean mInitialPrefValue1 = null; + @Nullable + private Boolean mInitialPrefValue2 = null; @Before public void setUp() { @@ -109,20 +117,28 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { mSharedPreferences = mContext.getSharedPreferences( LetterboxEduWindowManager.HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME, Context.MODE_PRIVATE); - mPrefKey = String.valueOf(mContext.getUserId()); - if (mSharedPreferences.contains(mPrefKey)) { - mInitialPrefValue = mSharedPreferences.getBoolean(mPrefKey, /* default= */ false); - mSharedPreferences.edit().remove(mPrefKey).apply(); + if (mSharedPreferences.contains(PREF_KEY_1)) { + mInitialPrefValue1 = mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false); + mSharedPreferences.edit().remove(PREF_KEY_1).apply(); + } + if (mSharedPreferences.contains(PREF_KEY_2)) { + mInitialPrefValue2 = mSharedPreferences.getBoolean(PREF_KEY_2, /* default= */ false); + mSharedPreferences.edit().remove(PREF_KEY_2).apply(); } } @After public void tearDown() { SharedPreferences.Editor editor = mSharedPreferences.edit(); - if (mInitialPrefValue == null) { - editor.remove(mPrefKey); + if (mInitialPrefValue1 == null) { + editor.remove(PREF_KEY_1); } else { - editor.putBoolean(mPrefKey, mInitialPrefValue); + editor.putBoolean(PREF_KEY_1, mInitialPrefValue1); + } + if (mInitialPrefValue2 == null) { + editor.remove(PREF_KEY_2); + } else { + editor.putBoolean(PREF_KEY_2, mInitialPrefValue2); } editor.apply(); } @@ -137,19 +153,9 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test - public void testCreateLayout_alreadyShownToUser_doesNotCreateLayout() { - LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); - mSharedPreferences.edit().putBoolean(mPrefKey, true).apply(); - - assertFalse(windowManager.createLayout(/* canShow= */ true)); - - assertNull(windowManager.mLayout); - } - - @Test public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ - true, /* isTaskbarEduShowing= */ true); + true, USER_ID_1, /* isTaskbarEduShowing= */ true); assertFalse(windowManager.createLayout(/* canShow= */ true)); @@ -162,7 +168,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { assertTrue(windowManager.createLayout(/* canShow= */ false)); - assertFalse(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false)); + assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false)); assertNull(windowManager.mLayout); } @@ -172,7 +178,6 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { assertTrue(windowManager.createLayout(/* canShow= */ true)); - assertTrue(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false)); LetterboxEduDialogLayout layout = windowManager.mLayout; assertNotNull(layout); verify(mViewHost).setView(eq(layout), mWindowAttrsCaptor.capture()); @@ -183,6 +188,8 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { assertNotNull(dialogTitle); spyOn(dialogTitle); + // The education shouldn't be marked as seen until enter animation is done. + assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false)); // Clicking the layout does nothing until enter animation is done. layout.performClick(); verify(mAnimationController, never()).startExitAnimation(any(), any()); @@ -191,6 +198,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { verifyAndFinishEnterAnimation(layout); + assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false)); verify(dialogTitle).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); // Exit animation should start following a click on the layout. layout.performClick(); @@ -208,12 +216,41 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() { + LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true, + USER_ID_1, /* isTaskbarEduShowing= */ false); + + assertTrue(windowManager.createLayout(/* canShow= */ true)); + + assertNotNull(windowManager.mLayout); + verifyAndFinishEnterAnimation(windowManager.mLayout); + assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false)); + + windowManager.release(); + windowManager = createWindowManager(/* eligible= */ true, + USER_ID_1, /* isTaskbarEduShowing= */ false); + + assertFalse(windowManager.createLayout(/* canShow= */ true)); + assertNull(windowManager.mLayout); + + clearInvocations(mTransitions, mAnimationController); + + windowManager = createWindowManager(/* eligible= */ true, + USER_ID_2, /* isTaskbarEduShowing= */ false); + + assertTrue(windowManager.createLayout(/* canShow= */ true)); + + assertNotNull(windowManager.mLayout); + verifyAndFinishEnterAnimation(windowManager.mLayout); + assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false)); + } + + @Test public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); assertTrue(windowManager.createLayout(/* canShow= */ true)); - - assertTrue(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false)); + assertNotNull(windowManager.mLayout); verify(mTransitions).runOnIdle(mRunOnIdleCaptor.capture()); @@ -222,6 +259,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { mRunOnIdleCaptor.getValue().run(); verify(mAnimationController, never()).startEnterAnimation(any(), any()); + assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false)); } @Test @@ -233,7 +271,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { assertNotNull(layout); assertTrue(windowManager.updateCompatInfo( - createTaskInfo(/* eligible= */ true, new Rect(50, 25, 150, 75)), + createTaskInfo(/* eligible= */ true, USER_ID_1, new Rect(50, 25, 150, 75)), mTaskListener, /* canShow= */ true)); verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ 100, @@ -341,13 +379,13 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } private LetterboxEduWindowManager createWindowManager(boolean eligible) { - return createWindowManager(eligible, /* isTaskbarEduShowing= */ false); + return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false); } private LetterboxEduWindowManager createWindowManager(boolean eligible, - boolean isTaskbarEduShowing) { + int userId, boolean isTaskbarEduShowing) { LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext, - createTaskInfo(eligible), mSyncTransactionQueue, mTaskListener, + createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener, createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController); @@ -375,11 +413,16 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } private static TaskInfo createTaskInfo(boolean eligible) { - return createTaskInfo(eligible, new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT)); + return createTaskInfo(eligible, USER_ID_1); + } + + private static TaskInfo createTaskInfo(boolean eligible, int userId) { + return createTaskInfo(eligible, userId, new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT)); } - private static TaskInfo createTaskInfo(boolean eligible, Rect bounds) { + private static TaskInfo createTaskInfo(boolean eligible, int userId, Rect bounds) { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.userId = userId; taskInfo.taskId = TASK_ID; taskInfo.topActivityEligibleForLetterboxEducation = eligible; taskInfo.configuration.windowConfiguration.setBounds(bounds); diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java index e14e8791447a..657d839c15aa 100644 --- a/media/java/android/media/tv/DsmccResponse.java +++ b/media/java/android/media/tv/DsmccResponse.java @@ -122,11 +122,12 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel mBiopMessageType = BIOP_MESSAGE_TYPE_STREAM; mFileDescriptor = null; mChildList = null; - mEventIds = eventIds; - mEventNames = eventNames; - if (mEventIds.length != eventNames.length) { + if (!((eventIds != null && eventNames != null && eventIds.length == eventNames.length) + || (eventIds == null && eventNames == null))) { throw new IllegalStateException("The size of eventIds and eventNames must be equal"); } + mEventIds = eventIds; + mEventNames = eventNames; } private DsmccResponse(@NonNull Parcel source) { @@ -137,10 +138,13 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY: case BIOP_MESSAGE_TYPE_DIRECTORY: int childNum = source.readInt(); - mChildList = new ArrayList<>(); - for (int i = 0; i < childNum; i++) { - mChildList.add(source.readString()); - } + if (childNum > 0) { + mChildList = new ArrayList<>(); + for (int i = 0; i < childNum; i++) { + mChildList.add(source.readString()); + } + } else + mChildList = null; mFileDescriptor = null; mEventIds = null; mEventNames = null; @@ -153,11 +157,16 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel break; case BIOP_MESSAGE_TYPE_STREAM: int eventNum = source.readInt(); - mEventIds = new int[eventNum]; - mEventNames = new String[eventNum]; - for (int i = 0; i < eventNum; i++) { - mEventIds[i] = source.readInt(); - mEventNames[i] = source.readString(); + if (eventNum > 0) { + mEventIds = new int[eventNum]; + mEventNames = new String[eventNum]; + for (int i = 0; i < eventNum; i++) { + mEventIds[i] = source.readInt(); + mEventNames[i] = source.readString(); + } + } else { + mEventIds = null; + mEventNames = null; } mChildList = null; mFileDescriptor = null; @@ -196,7 +205,7 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel && !mBiopMessageType.equals(BIOP_MESSAGE_TYPE_SERVICE_GATEWAY)) { throw new IllegalStateException("Not directory object"); } - return new ArrayList<String>(mChildList); + return mChildList != null ? new ArrayList<String>(mChildList) : new ArrayList<String>(); } /** @@ -207,7 +216,7 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) { throw new IllegalStateException("Not stream event object"); } - return mEventIds; + return mEventIds != null ? mEventIds : new int[0]; } /** @@ -218,7 +227,7 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) { throw new IllegalStateException("Not stream event object"); } - return mEventNames; + return mEventNames != null ? mEventNames : new String[0]; } @Override @@ -233,20 +242,26 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel switch (mBiopMessageType) { case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY: case BIOP_MESSAGE_TYPE_DIRECTORY: - dest.writeInt(mChildList.size()); - for (String child : mChildList) { - dest.writeString(child); - } + if (mChildList != null && mChildList.size() > 0) { + dest.writeInt(mChildList.size()); + for (String child : mChildList) { + dest.writeString(child); + } + } else + dest.writeInt(0); break; case BIOP_MESSAGE_TYPE_FILE: dest.writeFileDescriptor(mFileDescriptor.getFileDescriptor()); break; case BIOP_MESSAGE_TYPE_STREAM: - dest.writeInt(mEventIds.length); - for (int i = 0; i < mEventIds.length; i++) { - dest.writeInt(mEventIds[i]); - dest.writeString(mEventNames[i]); - } + if (mEventIds != null && mEventIds.length > 0) { + dest.writeInt(mEventIds.length); + for (int i = 0; i < mEventIds.length; i++) { + dest.writeInt(mEventIds[i]); + dest.writeString(mEventNames[i]); + } + } else + dest.writeInt(0); break; default: throw new IllegalStateException("unexpected BIOP message type"); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 4f7b7115fe26..2f4dd8fad8bb 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -1003,19 +1003,9 @@ DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) { } static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) { + sp<IDrm> drm = android::DrmUtils::MakeDrm(); std::vector<uint8_t> bv; - for (auto &factory : DrmUtils::MakeDrmFactories()) { - sp<drm::V1_3::IDrmFactory> factoryV1_3 = drm::V1_3::IDrmFactory::castFrom(factory); - if (factoryV1_3 == nullptr) { - continue; - } - factoryV1_3->getSupportedCryptoSchemes( - [&](const hardware::hidl_vec<hardware::hidl_array<uint8_t, 16>>& schemes) { - for (const auto &scheme : schemes) { - bv.insert(bv.end(), scheme.data(), scheme.data() + scheme.size()); - } - }); - } + drm->getSupportedSchemes(bv); jbyteArray jUuidBytes = env->NewByteArray(bv.size()); env->SetByteArrayRegion(jUuidBytes, 0, bv.size(), reinterpret_cast<const jbyte *>(bv.data())); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 0bdf65d8ef55..5eeb167ee5e0 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -20,6 +20,12 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER; import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; +import static android.companion.CompanionDeviceManager.REASON_CANCELED; +import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT; +import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED; +import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT; +import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR; +import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState; @@ -89,9 +95,6 @@ public class CompanionDeviceActivity extends FragmentActivity implements private static final String FRAGMENT_DIALOG_TAG = "fragment_dialog"; - // Activity result: Internal Error. - private static final int RESULT_INTERNAL_ERROR = 2; - // AssociationRequestsProcessor -> UI private static final int RESULT_CODE_ASSOCIATION_CREATED = 0; private static final String EXTRA_ASSOCIATION = "association"; @@ -209,7 +212,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements // TODO: handle config changes without cancelling. if (!isDone()) { - cancel(false); // will finish() + cancel(/* discoveryTimeOut */ false, /* userRejected */ false); // will finish() } } @@ -293,7 +296,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements private void onDiscoveryStateChanged(DiscoveryState newState) { if (newState == FINISHED_TIMEOUT && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) { - cancel(true); + cancel(/* discoveryTimeOut */ true, /* userRejected */ false); } } @@ -336,10 +339,12 @@ public class CompanionDeviceActivity extends FragmentActivity implements setResultAndFinish(association, RESULT_OK); } - private void cancel(boolean discoveryTimeout) { + private void cancel(boolean discoveryTimeout, boolean userRejected) { if (DEBUG) { - Log.i(TAG, "cancel(), discoveryTimeout=" + discoveryTimeout, - new Exception("Stack Trace Dump")); + Log.i(TAG, "cancel(), discoveryTimeout=" + + discoveryTimeout + + ", userRejected=" + + userRejected, new Exception("Stack Trace Dump")); } if (isDone()) { @@ -353,14 +358,27 @@ public class CompanionDeviceActivity extends FragmentActivity implements CompanionDeviceDiscoveryService.stop(this); } + final String cancelReason; + final int resultCode; + if (userRejected) { + cancelReason = REASON_USER_REJECTED; + resultCode = RESULT_USER_REJECTED; + } else if (discoveryTimeout) { + cancelReason = REASON_DISCOVERY_TIMEOUT; + resultCode = RESULT_DISCOVERY_TIMEOUT; + } else { + cancelReason = REASON_CANCELED; + resultCode = RESULT_CANCELED; + } + // First send callback to the app directly... try { - mAppCallback.onFailure(discoveryTimeout ? "Timeout." : "Cancelled."); + mAppCallback.onFailure(cancelReason); } catch (RemoteException ignore) { } // ... then set result and finish ("sending" onActivityResult()). - setResultAndFinish(null, RESULT_CANCELED); + setResultAndFinish(null, resultCode); } private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) { @@ -562,7 +580,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements // Disable the button, to prevent more clicks. v.setEnabled(false); - cancel(false); + cancel(/* discoveryTimeout */ false, /* userRejected */ true); } private void onShowHelperDialog(View view) { diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java index 512fbcee9330..209f372fd377 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java @@ -45,6 +45,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.Objects; +import java.util.concurrent.Executor; /** * The Network Service Discovery Manager class provides the API to discover services @@ -285,8 +286,12 @@ public final class NsdManager { private final Context mContext; private int mListenerKey = FIRST_LISTENER_KEY; + @GuardedBy("mMapLock") private final SparseArray mListenerMap = new SparseArray(); + @GuardedBy("mMapLock") private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>(); + @GuardedBy("mMapLock") + private final SparseArray<Executor> mExecutorMap = new SparseArray<>(); private final Object mMapLock = new Object(); // Map of listener key sent by client -> per-network discovery tracker @GuardedBy("mPerNetworkDiscoveryMap") @@ -299,6 +304,7 @@ public final class NsdManager { final String mServiceType; final int mProtocolType; final DiscoveryListener mBaseListener; + final Executor mBaseExecutor; final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners = new ArrayMap<>(); @@ -308,7 +314,8 @@ public final class NsdManager { final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener( network, mBaseListener); mPerNetworkListeners.put(network, wrappedListener); - discoverServices(mServiceType, mProtocolType, network, wrappedListener); + discoverServices(mServiceType, mProtocolType, network, mBaseExecutor, + wrappedListener); } @Override @@ -355,9 +362,10 @@ public final class NsdManager { } private PerNetworkDiscoveryTracker(String serviceType, int protocolType, - DiscoveryListener baseListener) { + Executor baseExecutor, DiscoveryListener baseListener) { mServiceType = serviceType; mProtocolType = protocolType; + mBaseExecutor = baseExecutor; mBaseListener = baseListener; } @@ -644,9 +652,11 @@ public final class NsdManager { final int key = message.arg2; final Object listener; final NsdServiceInfo ns; + final Executor executor; synchronized (mMapLock) { listener = mListenerMap.get(key); ns = mServiceMap.get(key); + executor = mExecutorMap.get(key); } if (listener == null) { Log.d(TAG, "Stale key " + message.arg2); @@ -657,56 +667,64 @@ public final class NsdManager { } switch (what) { case DISCOVER_SERVICES_STARTED: - String s = getNsdServiceInfoType((NsdServiceInfo) message.obj); - ((DiscoveryListener) listener).onDiscoveryStarted(s); + final String s = getNsdServiceInfoType((NsdServiceInfo) message.obj); + executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s)); break; case DISCOVER_SERVICES_FAILED: removeListener(key); - ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns), - message.arg1); + executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed( + getNsdServiceInfoType(ns), message.arg1)); break; case SERVICE_FOUND: - ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj); + executor.execute(() -> ((DiscoveryListener) listener).onServiceFound( + (NsdServiceInfo) message.obj)); break; case SERVICE_LOST: - ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj); + executor.execute(() -> ((DiscoveryListener) listener).onServiceLost( + (NsdServiceInfo) message.obj)); break; case STOP_DISCOVERY_FAILED: // TODO: failure to stop discovery should be internal and retried internally, as // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED removeListener(key); - ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns), - message.arg1); + executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed( + getNsdServiceInfoType(ns), message.arg1)); break; case STOP_DISCOVERY_SUCCEEDED: removeListener(key); - ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns)); + executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped( + getNsdServiceInfoType(ns))); break; case REGISTER_SERVICE_FAILED: removeListener(key); - ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1); + executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed( + ns, message.arg1)); break; case REGISTER_SERVICE_SUCCEEDED: - ((RegistrationListener) listener).onServiceRegistered( - (NsdServiceInfo) message.obj); + executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered( + (NsdServiceInfo) message.obj)); break; case UNREGISTER_SERVICE_FAILED: removeListener(key); - ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1); + executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed( + ns, message.arg1)); break; case UNREGISTER_SERVICE_SUCCEEDED: // TODO: do not unregister listener until service is unregistered, or provide // alternative way for unregistering ? removeListener(message.arg2); - ((RegistrationListener) listener).onServiceUnregistered(ns); + executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered( + ns)); break; case RESOLVE_SERVICE_FAILED: removeListener(key); - ((ResolveListener) listener).onResolveFailed(ns, message.arg1); + executor.execute(() -> ((ResolveListener) listener).onResolveFailed( + ns, message.arg1)); break; case RESOLVE_SERVICE_SUCCEEDED: removeListener(key); - ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj); + executor.execute(() -> ((ResolveListener) listener).onServiceResolved( + (NsdServiceInfo) message.obj)); break; default: Log.d(TAG, "Ignored " + message); @@ -722,7 +740,7 @@ public final class NsdManager { } // Assert that the listener is not in the map, then add it and returns its key - private int putListener(Object listener, NsdServiceInfo s) { + private int putListener(Object listener, Executor e, NsdServiceInfo s) { checkListener(listener); final int key; synchronized (mMapLock) { @@ -733,6 +751,7 @@ public final class NsdManager { key = nextListenerKey(); mListenerMap.put(key, listener); mServiceMap.put(key, s); + mExecutorMap.put(key, e); } return key; } @@ -741,6 +760,7 @@ public final class NsdManager { synchronized (mMapLock) { mListenerMap.remove(key); mServiceMap.remove(key); + mExecutorMap.remove(key); } } @@ -779,12 +799,33 @@ public final class NsdManager { */ public void registerService(NsdServiceInfo serviceInfo, int protocolType, RegistrationListener listener) { + registerService(serviceInfo, protocolType, Runnable::run, listener); + } + + /** + * Register a service to be discovered by other services. + * + * <p> The function call immediately returns after sending a request to register service + * to the framework. The application is notified of a successful registration + * through the callback {@link RegistrationListener#onServiceRegistered} or a failure + * through {@link RegistrationListener#onRegistrationFailed}. + * + * <p> The application should call {@link #unregisterService} when the service + * registration is no longer required, and/or whenever the application is stopped. + * @param serviceInfo The service being registered + * @param protocolType The service discovery protocol + * @param executor Executor to run listener callbacks with + * @param listener The listener notifies of a successful registration and is used to + * unregister this service through a call on {@link #unregisterService}. Cannot be null. + */ + public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType, + @NonNull Executor executor, @NonNull RegistrationListener listener) { if (serviceInfo.getPort() <= 0) { throw new IllegalArgumentException("Invalid port number"); } checkServiceInfo(serviceInfo); checkProtocol(protocolType); - int key = putListener(listener, serviceInfo); + int key = putListener(listener, executor, serviceInfo); try { mService.registerService(key, serviceInfo); } catch (RemoteException e) { @@ -815,11 +856,35 @@ public final class NsdManager { } /** - * Same as {@link #discoverServices(String, int, Network, DiscoveryListener)} with a null - * {@link Network}. + * Initiate service discovery to browse for instances of a service type. Service discovery + * consumes network bandwidth and will continue until the application calls + * {@link #stopServiceDiscovery}. + * + * <p> The function call immediately returns after sending a request to start service + * discovery to the framework. The application is notified of a success to initiate + * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure + * through {@link DiscoveryListener#onStartDiscoveryFailed}. + * + * <p> Upon successful start, application is notified when a service is found with + * {@link DiscoveryListener#onServiceFound} or when a service is lost with + * {@link DiscoveryListener#onServiceLost}. + * + * <p> Upon failure to start, service discovery is not active and application does + * not need to invoke {@link #stopServiceDiscovery} + * + * <p> The application should call {@link #stopServiceDiscovery} when discovery of this + * service type is no longer required, and/or whenever the application is paused or + * stopped. + * + * @param serviceType The service type being discovered. Examples include "_http._tcp" for + * http services or "_ipp._tcp" for printers + * @param protocolType The service discovery protocol + * @param listener The listener notifies of a successful discovery and is used + * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. + * Cannot be null. Cannot be in use for an active service discovery. */ public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) { - discoverServices(serviceType, protocolType, (Network) null, listener); + discoverServices(serviceType, protocolType, (Network) null, Runnable::run, listener); } /** @@ -842,17 +907,17 @@ public final class NsdManager { * <p> The application should call {@link #stopServiceDiscovery} when discovery of this * service type is no longer required, and/or whenever the application is paused or * stopped. - * * @param serviceType The service type being discovered. Examples include "_http._tcp" for * http services or "_ipp._tcp" for printers * @param protocolType The service discovery protocol * @param network Network to discover services on, or null to discover on all available networks + * @param executor Executor to run listener callbacks with * @param listener The listener notifies of a successful discovery and is used * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. - * Cannot be null. Cannot be in use for an active service discovery. */ public void discoverServices(@NonNull String serviceType, int protocolType, - @Nullable Network network, @NonNull DiscoveryListener listener) { + @Nullable Network network, @NonNull Executor executor, + @NonNull DiscoveryListener listener) { if (TextUtils.isEmpty(serviceType)) { throw new IllegalArgumentException("Service type cannot be empty"); } @@ -862,7 +927,7 @@ public final class NsdManager { s.setServiceType(serviceType); s.setNetwork(network); - int key = putListener(listener, s); + int key = putListener(listener, executor, s); try { mService.discoverServices(key, s); } catch (RemoteException e) { @@ -899,18 +964,18 @@ public final class NsdManager { * themselves are encouraged to use this method instead of other overloads of * {@code discoverServices}, as they will receive proper notifications when a service becomes * available or unavailable due to network changes. - * * @param serviceType The service type being discovered. Examples include "_http._tcp" for * http services or "_ipp._tcp" for printers * @param protocolType The service discovery protocol * @param networkRequest Request specifying networks that should be considered when discovering + * @param executor Executor to run listener callbacks with * @param listener The listener notifies of a successful discovery and is used * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. - * Cannot be null. Cannot be in use for an active service discovery. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String serviceType, int protocolType, - @NonNull NetworkRequest networkRequest, @NonNull DiscoveryListener listener) { + @NonNull NetworkRequest networkRequest, @NonNull Executor executor, + @NonNull DiscoveryListener listener) { if (TextUtils.isEmpty(serviceType)) { throw new IllegalArgumentException("Service type cannot be empty"); } @@ -920,10 +985,10 @@ public final class NsdManager { NsdServiceInfo s = new NsdServiceInfo(); s.setServiceType(serviceType); - final int baseListenerKey = putListener(listener, s); + final int baseListenerKey = putListener(listener, executor, s); final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker( - serviceType, protocolType, listener); + serviceType, protocolType, executor, listener); synchronized (mPerNetworkDiscoveryMap) { mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo); @@ -974,8 +1039,21 @@ public final class NsdManager { * Cannot be in use for an active service resolution. */ public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) { + resolveService(serviceInfo, Runnable::run, listener); + } + + /** + * Resolve a discovered service. An application can resolve a service right before + * establishing a connection to fetch the IP and port details on which to setup + * the connection. + * @param serviceInfo service to be resolved + * @param executor Executor to run listener callbacks with + * @param listener to receive callback upon success or failure. + */ + public void resolveService(@NonNull NsdServiceInfo serviceInfo, + @NonNull Executor executor, @NonNull ResolveListener listener) { checkServiceInfo(serviceInfo); - int key = putListener(listener, serviceInfo); + int key = putListener(listener, executor, serviceInfo); try { mService.resolveService(key, serviceInfo); } catch (RemoteException e) { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index b439f8421b73..998aeeab4b47 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -23,9 +23,11 @@ import android.os.AsyncTask; import android.os.Build; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; +import android.os.SystemProperties; import android.os.image.DynamicSystemManager; import android.service.persistentdata.PersistentDataBlockManager; import android.util.Log; +import android.util.Range; import android.webkit.URLUtil; import org.json.JSONException; @@ -48,7 +50,12 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { private static final String TAG = "InstallationAsyncTask"; - private static final int READ_BUFFER_SIZE = 1 << 13; + private static final int MIN_SHARED_MEMORY_SIZE = 8 << 10; // 8KiB + private static final int MAX_SHARED_MEMORY_SIZE = 1024 << 10; // 1MiB + private static final int DEFAULT_SHARED_MEMORY_SIZE = 64 << 10; // 64KiB + private static final String SHARED_MEMORY_SIZE_PROP = + "dynamic_system.data_transfer.shared_memory.size"; + private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27; private static final List<String> UNSUPPORTED_PARTITIONS = @@ -131,6 +138,7 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { void onResult(int resultCode, Throwable detail); } + private final int mSharedMemorySize; private final String mUrl; private final String mDsuSlot; private final String mPublicKey; @@ -164,6 +172,11 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { Context context, DynamicSystemManager dynSystem, ProgressListener listener) { + mSharedMemorySize = + Range.create(MIN_SHARED_MEMORY_SIZE, MAX_SHARED_MEMORY_SIZE) + .clamp( + SystemProperties.getInt( + SHARED_MEMORY_SIZE_PROP, DEFAULT_SHARED_MEMORY_SIZE)); mUrl = url; mDsuSlot = dsuSlot; mPublicKey = publicKey; @@ -541,10 +554,10 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { Log.d(TAG, "Start installing: " + partitionName); - MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, READ_BUFFER_SIZE); + MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, mSharedMemorySize); ParcelFileDescriptor pfd = new ParcelFileDescriptor(memoryFile.getFileDescriptor()); - mInstallationSession.setAshmem(pfd, READ_BUFFER_SIZE); + mInstallationSession.setAshmem(pfd, memoryFile.length()); mPartitionName = partitionName; mPartitionSize = partitionSize; @@ -553,10 +566,10 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { long prevInstalledSize = 0; long installedSize = 0; - byte[] bytes = new byte[READ_BUFFER_SIZE]; + byte[] bytes = new byte[memoryFile.length()]; int numBytesRead; - while ((numBytesRead = sis.read(bytes, 0, READ_BUFFER_SIZE)) != -1) { + while ((numBytesRead = sis.read(bytes, 0, bytes.length)) != -1) { if (isCancelled()) { return; } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java index 4117d0f07e0f..7d2326693a17 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java @@ -133,36 +133,32 @@ public class SparseInputStream extends InputStream { return mLeft == 0; } - /** - * It overrides the InputStream.read(byte[] buf) - */ - public int read(byte[] buf) throws IOException { + @Override + public int read(byte[] buf, int off, int len) throws IOException { if (!mIsSparse) { - return mIn.read(buf); + return mIn.read(buf, off, len); } if (prepareChunk()) return -1; int n = -1; switch (mCur.mChunkType) { case SparseChunk.RAW: - n = mIn.read(buf, 0, (int) min(mLeft, buf.length)); + n = mIn.read(buf, off, (int) min(mLeft, len)); mLeft -= n; return n; case SparseChunk.DONTCARE: - n = (int) min(mLeft, buf.length); - Arrays.fill(buf, 0, n - 1, (byte) 0); + n = (int) min(mLeft, len); + Arrays.fill(buf, off, off + n, (byte) 0); mLeft -= n; return n; case SparseChunk.FILL: // The FILL type is rarely used, so use a simple implmentation. - return super.read(buf); + return super.read(buf, off, len); default: throw new IOException("Unsupported Chunk:" + mCur.toString()); } } - /** - * It overrides the InputStream.read() - */ + @Override public int read() throws IOException { if (!mIsSparse) { return mIn.read(); diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java index 543a5a0fa772..77d65834da37 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java @@ -63,8 +63,7 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { return; } - mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback()); - View view = mToolbardelegate.onCreateView(getLayoutInflater(), null); + View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null); super.setContentView(view); } @@ -107,7 +106,7 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { @Override public void setTitle(CharSequence title) { - mToolbardelegate.setTitle(title); + getToolbarDelegate().setTitle(title); } @Override @@ -128,7 +127,7 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { */ @Nullable public CollapsingToolbarLayout getCollapsingToolbarLayout() { - return mToolbardelegate.getCollapsingToolbarLayout(); + return getToolbarDelegate().getCollapsingToolbarLayout(); } /** @@ -136,6 +135,13 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { */ @Nullable public AppBarLayout getAppBarLayout() { - return mToolbardelegate.getAppBarLayout(); + return getToolbarDelegate().getAppBarLayout(); + } + + private CollapsingToolbarDelegate getToolbarDelegate() { + if (mToolbardelegate == null) { + mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback()); + } + return mToolbardelegate; } } diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_text_color_secondary.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_text_color_secondary.xml new file mode 100644 index 000000000000..98ea3eabe5c1 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_text_color_secondary.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?android:attr/disabledAlpha" + android:color="@color/settingslib_text_color_secondary_device_default"/> + <item android:color="@color/settingslib_text_color_secondary_device_default"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml index cba1a9cf0003..87cb41863528 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml @@ -24,7 +24,7 @@ <style name="TextAppearance.PreferenceSummary.SettingsLib" parent="@android:style/TextAppearance.DeviceDefault.Small"> - <item name="android:textColor">@color/settingslib_text_color_secondary_device_default</item> + <item name="android:textColor">@color/settingslib_text_color_secondary</item> </style> <style name="TextAppearance.CategoryTitle.SettingsLib" diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 3a3ba9e9bdd7..1c1e1ba32f00 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -229,11 +229,11 @@ <string name="bluetooth_active_no_battery_level">Active</string> <!-- Connected device settings. Message when the left-side hearing aid device is active. [CHAR LIMIT=NONE] --> - <string name="bluetooth_hearing_aid_left_active">Active, left ear</string> + <string name="bluetooth_hearing_aid_left_active">Active, left only</string> <!-- Connected device settings. Message when the right-side hearing aid device is active. [CHAR LIMIT=NONE] --> - <string name="bluetooth_hearing_aid_right_active">Active, right ear</string> + <string name="bluetooth_hearing_aid_right_active">Active, right only</string> <!-- Connected device settings. Message when the left-side and right-side hearing aids device are active. [CHAR LIMIT=NONE] --> - <string name="bluetooth_hearing_aid_left_and_right_active">Active, left and right ears</string> + <string name="bluetooth_hearing_aid_left_and_right_active">Active, left and right</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. --> <string name="bluetooth_profile_a2dp">Media audio</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java new file mode 100644 index 000000000000..3ce7a0e4efa5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +public final class BluetoothBroadcastUtils { + + static final String SCHEME_BT_BROADCAST_METADATA = "BT:"; + + // BluetoothLeBroadcastMetadata + static final String PREFIX_BT_ADDRESS_TYPE = "T:"; + static final String PREFIX_BT_DEVICE = "D:"; + static final String PREFIX_BT_ADVERTISING_SID = "AS:"; + static final String PREFIX_BT_BROADCAST_ID = "B:"; + static final String PREFIX_BT_SYNC_INTERVAL = "SI:"; + static final String PREFIX_BT_IS_ENCRYPTED = "E:"; + static final String PREFIX_BT_BROADCAST_CODE = "C:"; + static final String PREFIX_BT_PRESENTATION_DELAY = "D:"; + static final String PREFIX_BT_SUBGROUPS = "G:"; + static final String PREFIX_BT_ANDROID_VERSION = "V:"; + + // BluetoothLeBroadcastSubgroup + static final String PREFIX_BTSG_CODEC_ID = "CID:"; + static final String PREFIX_BTSG_CODEC_CONFIG = "CC:"; + static final String PREFIX_BTSG_AUDIO_CONTENT = "AC:"; + static final String PREFIX_BTSG_CHANNEL_PREF = "CP:"; + static final String PREFIX_BTSG_BROADCAST_CHANNEL = "BC:"; + + // BluetoothLeAudioCodecConfigMetadata + static final String PREFIX_BTCC_AUDIO_LOCATION = "AL:"; + static final String PREFIX_BTCC_RAW_METADATA = "CCRM:"; + + // BluetoothLeAudioContentMetadata + static final String PREFIX_BTAC_PROGRAM_INFO = "PI:"; + static final String PREFIX_BTAC_LANGUAGE = "L:"; + static final String PREFIX_BTAC_RAW_METADATA = "ACRM:"; + + // BluetoothLeBroadcastChannel + static final String PREFIX_BTBC_CHANNEL_INDEX = "CI:"; + static final String PREFIX_BTBC_CODEC_CONFIG = "BCCM:"; + + static final String DELIMITER_QR_CODE = ";"; +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java new file mode 100644 index 000000000000..9df0f4df1c87 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothLeAudioContentMetadata; + +public class LocalBluetoothLeAudioContentMetadata { + + private static final String TAG = "LocalBluetoothLeAudioContentMetadata"; + private final BluetoothLeAudioContentMetadata mContentMetadata; + private final String mLanguage; + private final byte[] mRawMetadata; + private String mProgramInfo; + + LocalBluetoothLeAudioContentMetadata(BluetoothLeAudioContentMetadata contentMetadata) { + mContentMetadata = contentMetadata; + mProgramInfo = contentMetadata.getProgramInfo(); + mLanguage = contentMetadata.getLanguage(); + mRawMetadata = contentMetadata.getRawMetadata(); + } + + public void setProgramInfo(String programInfo) { + mProgramInfo = programInfo; + } + + public String getProgramInfo() { + return mProgramInfo; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java new file mode 100644 index 000000000000..bb47c5e10711 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -0,0 +1,192 @@ +/* + * 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.settingslib.bluetooth; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothLeAudioContentMetadata; +import android.bluetooth.BluetoothLeBroadcast; +import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothProfile.ServiceListener; +import android.content.Context; +import android.util.Log; + +/** + * LocalBluetoothLeBroadcast provides an interface between the Settings app + * and the functionality of the local {@link BluetoothLeBroadcast}. + */ +public class LocalBluetoothLeBroadcast implements BluetoothLeBroadcast.Callback { + + private static final String TAG = "LocalBluetoothLeBroadcast"; + private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; + private static final boolean DEBUG = BluetoothUtils.D; + + private BluetoothLeBroadcast mBluetoothLeBroadcast; + private LocalBluetoothProfileManager mProfileManager; + private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata; + private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; + private BluetoothLeAudioContentMetadata.Builder mBuilder; + private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; + private boolean mIsProfileReady; + + private final ServiceListener mServiceListener = new ServiceListener() { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) { + if (DEBUG) { + Log.d(TAG,"Bluetooth service connected"); + } + mBluetoothLeBroadcast = (BluetoothLeBroadcast) proxy; + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady = true; + } + } + + @Override + public void onServiceDisconnected(int profile) { + if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) { + if (DEBUG) { + Log.d(TAG,"Bluetooth service disconnected"); + } + mIsProfileReady = false; + } + } + }; + + LocalBluetoothLeBroadcast(Context context, LocalBluetoothProfileManager profileManager) { + mProfileManager = profileManager; + BluetoothAdapter.getDefaultAdapter(). + getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); + mBuilder = new BluetoothLeAudioContentMetadata.Builder(); + } + + public void startBroadcast(byte[] broadcastCode, String language, + String programInfo) { + if (DEBUG) { + if (mBluetoothLeBroadcast == null) { + Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast."); + return; + } + Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo); + } + buildContentMetadata(language, programInfo); + mBluetoothLeBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata, broadcastCode); + } + + public void stopBroadcast() { + if (DEBUG) { + if (mBluetoothLeBroadcast == null) { + Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast."); + return; + } + Log.d(TAG, "stopBroadcast()"); + } + mBluetoothLeBroadcast.stopBroadcast(mBroadcastId); + } + + public void updateBroadcast(String language, String programInfo) { + if (DEBUG) { + if (mBluetoothLeBroadcast == null) { + Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast."); + return; + } + Log.d(TAG, + "updateBroadcast: language = " + language + " ,programInfo = " + programInfo); + } + mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build(); + mBluetoothLeBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata); + } + + private void buildContentMetadata(String language, String programInfo) { + mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo( + programInfo).build(); + } + + public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() { + return new LocalBluetoothLeBroadcastMetadata(mBluetoothLeBroadcastMetadata); + } + + @Override + public void onBroadcastStarted(int reason, int broadcastId) { + if (DEBUG) { + Log.d(TAG, + "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + broadcastId); + } + } + + @Override + public void onBroadcastStartFailed(int reason) { + if (DEBUG) { + Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason); + } + } + + @Override + public void onBroadcastMetadataChanged(int broadcastId, + @NonNull BluetoothLeBroadcastMetadata metadata) { + if (DEBUG) { + Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId); + } + mBluetoothLeBroadcastMetadata = metadata; + } + + @Override + public void onBroadcastStopped(int reason, int broadcastId) { + if (DEBUG) { + Log.d(TAG, + "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + broadcastId); + } + } + + @Override + public void onBroadcastStopFailed(int reason) { + if (DEBUG) { + Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason); + } + } + + @Override + public void onBroadcastUpdated(int reason, int broadcastId) { + if (DEBUG) { + Log.d(TAG, + "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + broadcastId); + } + } + + @Override + public void onBroadcastUpdateFailed(int reason, int broadcastId) { + if (DEBUG) { + Log.d(TAG, + "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = " + + broadcastId); + } + } + + @Override + public void onPlaybackStarted(int reason, int broadcastId) { + } + + @Override + public void onPlaybackStopped(int reason, int broadcastId) { + } + + public boolean isProfileReady() { + return mIsProfileReady; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java new file mode 100644 index 000000000000..d904265efda9 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -0,0 +1,214 @@ +/* + * 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.settingslib.bluetooth; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcast; +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; +import android.util.Log; + +import java.util.List; + +/** + * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app + * and the functionality of the local {@link BluetoothLeBroadcastAssistant}. + */ +public class LocalBluetoothLeBroadcastAssistant implements + BluetoothLeBroadcastAssistant.Callback { + + private static final String TAG = "LocalBluetoothLeBroadcastAssistant"; + private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; + private static final boolean DEBUG = BluetoothUtils.D; + + private LocalBluetoothProfileManager mProfileManager; + private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant; + private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; + private BluetoothLeBroadcastMetadata.Builder mBuilder; + private boolean mIsProfileReady; + + private final ServiceListener mServiceListener = new ServiceListener() { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) { + if (DEBUG) { + Log.d(TAG,"Bluetooth service connected"); + } + mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy; + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady = true; + } + } + + @Override + public void onServiceDisconnected(int profile) { + if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) { + if (DEBUG) { + Log.d(TAG,"Bluetooth service disconnected"); + } + mIsProfileReady = false; + } + } + }; + + LocalBluetoothLeBroadcastAssistant(Context context, + LocalBluetoothProfileManager profileManager) { + mProfileManager = profileManager; + BluetoothAdapter.getDefaultAdapter(). + getProfileProxy(context, mServiceListener, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); + mBuilder = new BluetoothLeBroadcastMetadata.Builder(); + } + + public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType, + int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId, + int paSyncInterval, boolean isEncrypted, byte[] broadcastCode, + BluetoothDevice sourceDevice, boolean isGroupOp) { + if (DEBUG) { + Log.d(TAG, "addSource()"); + } + if (mBluetoothLeBroadcastAssistant == null) { + Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); + return ; + } + buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId, + paSyncInterval, isEncrypted, broadcastCode, sourceDevice); + mBluetoothLeBroadcastAssistant.addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp); + } + + private void buildMetadata(int sourceAddressType, int presentationDelayMicros, + int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted, + byte[] broadcastCode, BluetoothDevice sourceDevice) { + mBluetoothLeBroadcastMetadata = + mBuilder.setSourceDevice(sourceDevice, sourceAddressType) + .setSourceAdvertisingSid(sourceAdvertisingSid) + .setBroadcastId(broadcastId) + .setPaSyncInterval(paSyncInterval) + .setEncrypted(isEncrypted) + .setBroadcastCode(broadcastCode) + .setPresentationDelayMicros(presentationDelayMicros) + .build(); + } + + public void removeSource(@NonNull BluetoothDevice sink, int sourceId) { + if (DEBUG) { + Log.d(TAG, "removeSource()"); + } + if (mBluetoothLeBroadcastAssistant == null) { + Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); + return ; + } + mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId); + } + + public void startSearchingForSources(@NonNull List<android.bluetooth.le.ScanFilter> filters) { + if (DEBUG) { + Log.d(TAG, "startSearchingForSources()"); + } + if (mBluetoothLeBroadcastAssistant == null) { + Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); + return ; + } + mBluetoothLeBroadcastAssistant.startSearchingForSources(filters); + } + + @Override + public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) { + if (DEBUG) { + Log.d(TAG, "onSourceAdded(), reason = " + reason + " , sourceId = " + sourceId); + } + + } + + @Override + public void onSourceAddFailed(@NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastMetadata source, int reason) { + if (DEBUG) { + Log.d(TAG, "onSourceAddFailed(), reason = " + reason); + } + } + + @Override + public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, int reason) { + if (DEBUG) { + Log.d(TAG, "onSourceRemoved(), reason = " + reason + " , sourceId = " + sourceId); + } + } + + @Override + public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) { + if (DEBUG) { + Log.d(TAG, "onSourceRemoveFailed(), reason = " + reason + " , sourceId = " + sourceId); + } + } + + @Override + public void onSearchStarted(int reason) { + if (DEBUG) { + Log.d(TAG, "onSearchStarted(), reason = " + reason); + } + } + + @Override + public void onSearchStartFailed(int reason) { + if (DEBUG) { + Log.d(TAG, "onSearchStartFailed(), reason = " + reason); + } + } + + @Override + public void onSearchStopped(int reason) { + if (DEBUG) { + Log.d(TAG, "onSearchStopped(), reason = " + reason); + } + } + + @Override + public void onSearchStopFailed(int reason) { + if (DEBUG) { + Log.d(TAG, "onSearchStopFailed(), reason = " + reason); + } + } + + @Override + public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) { + } + + @Override + public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, int reason) { + } + + @Override + public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) { + } + + @Override + public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId, + @NonNull BluetoothLeBroadcastReceiveState state) { + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java new file mode 100644 index 000000000000..cf4ba8b46c7a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java @@ -0,0 +1,298 @@ +/* + * 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeAudioCodecConfigMetadata; +import android.bluetooth.BluetoothLeAudioContentMetadata; +import android.bluetooth.BluetoothLeBroadcastChannel; +import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.bluetooth.BluetoothLeBroadcastSubgroup; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LocalBluetoothLeBroadcastMetadata { + private static final boolean DEBUG = BluetoothUtils.D; + private static final String TAG = "LocalBluetoothLeBroadcastMetadata"; + private static final String METADATA_START = "<"; + private static final String METADATA_END = ">"; + private static final String PATTERN_REGEX = "<(.*?)>"; + + private BluetoothLeBroadcastSubgroup mSubgroup; + private List<BluetoothLeBroadcastSubgroup> mSubgroupList; + + // BluetoothLeBroadcastMetadata + // Optional: Identity address type + private int mSourceAddressType; + // Optional: Must use identity address + private BluetoothDevice mSourceDevice; + private int mSourceAdvertisingSid; + private int mBroadcastId; + private int mPaSyncInterval; + private int mPresentationDelayMicros; + private boolean mIsEncrypted; + private byte[] mBroadcastCode; + + // BluetoothLeBroadcastSubgroup + private long mCodecId; + private BluetoothLeAudioContentMetadata mContentMetadata; + private BluetoothLeAudioCodecConfigMetadata mConfigMetadata; + private BluetoothLeBroadcastChannel mChannel; + + // BluetoothLeAudioCodecConfigMetadata + private long mAudioLocation; + + // BluetoothLeAudioContentMetadata + private String mLanguage; + private String mProgramInfo; + + // BluetoothLeBroadcastChannel + private boolean mIsSelected; + private int mChannelIndex; + + + LocalBluetoothLeBroadcastMetadata(BluetoothLeBroadcastMetadata metadata) { + mSourceAddressType = metadata.getSourceAddressType(); + mSourceDevice = metadata.getSourceDevice(); + mSourceAdvertisingSid = metadata.getSourceAdvertisingSid(); + mBroadcastId = metadata.getBroadcastId(); + mPaSyncInterval = metadata.getPaSyncInterval(); + mIsEncrypted = metadata.isEncrypted(); + mBroadcastCode = metadata.getBroadcastCode(); + mPresentationDelayMicros = metadata.getPresentationDelayMicros(); + mSubgroupList = metadata.getSubgroups(); + } + + public void setBroadcastCode(byte[] code) { + mBroadcastCode = code; + } + + public int getBroadcastId() { + return mBroadcastId; + } + + public String convertToQrCodeString() { + return new StringBuilder() + .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA) + .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE) + .append(METADATA_START).append(mSourceAddressType).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BT_DEVICE) + .append(METADATA_START).append(mSourceDevice).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BT_ADVERTISING_SID) + .append(METADATA_START).append(mSourceAdvertisingSid).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_ID) + .append(METADATA_START).append(mBroadcastId).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BT_SYNC_INTERVAL) + .append(METADATA_START).append(mPaSyncInterval).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BT_IS_ENCRYPTED) + .append(METADATA_START).append(mIsEncrypted).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_CODE) + .append(METADATA_START).append(Arrays.toString(mBroadcastCode)).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BT_PRESENTATION_DELAY) + .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(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .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;; + * + * @return BluetoothLeBroadcastMetadata + */ + public BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) { + if (DEBUG) { + Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata"); + } + Pattern pattern = Pattern.compile(PATTERN_REGEX); + Matcher match = pattern.matcher(qrCodeString); + if (match.find()) { + ArrayList<String> resultList = new ArrayList<>(); + resultList.add(match.group(1)); + mSourceAddressType = Integer.parseInt(resultList.get(0)); + mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + resultList.get(1)); + mSourceAdvertisingSid = Integer.parseInt(resultList.get(2)); + mBroadcastId = Integer.parseInt(resultList.get(3)); + mPaSyncInterval = Integer.parseInt(resultList.get(4)); + mIsEncrypted = Boolean.valueOf(resultList.get(5)); + mBroadcastCode = resultList.get(6).getBytes(); + mPresentationDelayMicros = Integer.parseInt(resultList.get(7)); + mSubgroup = convertToSubgroup(resultList.get(8)); + + if (DEBUG) { + Log.d(TAG, "Converted qrCodeString result: " + match.group()); + } + + return new BluetoothLeBroadcastMetadata.Builder() + .setSourceDevice(mSourceDevice, mSourceAddressType) + .setSourceAdvertisingSid(mSourceAdvertisingSid) + .setBroadcastId(mBroadcastId) + .setPaSyncInterval(mPaSyncInterval) + .setEncrypted(mIsEncrypted) + .setBroadcastCode(mBroadcastCode) + .setPresentationDelayMicros(mPresentationDelayMicros) + .addSubgroup(mSubgroup) + .build(); + } else { + if (DEBUG) { + Log.d(TAG, + "The match fail, can not convert it to BluetoothLeBroadcastMetadata."); + } + return null; + } + } + + private BluetoothLeBroadcastSubgroup convertToSubgroup(String subgroupString) { + if (DEBUG) { + Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup"); + } + Pattern pattern = Pattern.compile(PATTERN_REGEX); + Matcher match = pattern.matcher(subgroupString); + if (match.find()) { + ArrayList<String> resultList = new ArrayList<>(); + resultList.add(match.group(1)); + mCodecId = Long.getLong(resultList.get(0)); + mConfigMetadata = convertToConfigMetadata(resultList.get(1)); + mContentMetadata = convertToContentMetadata(resultList.get(2)); + mChannel = convertToChannel(resultList.get(3), mConfigMetadata); + + if (DEBUG) { + Log.d(TAG, "Converted subgroupString result: " + match.group()); + } + + return new BluetoothLeBroadcastSubgroup.Builder() + .setCodecId(mCodecId) + .setCodecSpecificConfig(mConfigMetadata) + .setContentMetadata(mContentMetadata) + .addChannel(mChannel) + .build(); + } else { + if (DEBUG) { + Log.d(TAG, + "The match fail, can not convert it to BluetoothLeBroadcastSubgroup."); + } + return null; + } + } + + private BluetoothLeAudioCodecConfigMetadata convertToConfigMetadata( + String configMetadataString) { + if (DEBUG) { + Log.d(TAG, + "Convert " + configMetadataString + "to BluetoothLeAudioCodecConfigMetadata"); + } + Pattern pattern = Pattern.compile(PATTERN_REGEX); + Matcher match = pattern.matcher(configMetadataString); + if (match.find()) { + ArrayList<String> resultList = new ArrayList<>(); + resultList.add(match.group(1)); + mAudioLocation = Long.getLong(resultList.get(0)); + + if (DEBUG) { + Log.d(TAG, "Converted configMetadataString result: " + match.group()); + } + + return new BluetoothLeAudioCodecConfigMetadata.Builder() + .setAudioLocation(mAudioLocation) + .build(); + } else { + if (DEBUG) { + Log.d(TAG, + "The match fail, can not convert it to " + + "BluetoothLeAudioCodecConfigMetadata."); + } + return null; + } + } + + private BluetoothLeAudioContentMetadata convertToContentMetadata(String contentMetadataString) { + if (DEBUG) { + Log.d(TAG, "Convert " + contentMetadataString + "to BluetoothLeAudioContentMetadata"); + } + Pattern pattern = Pattern.compile(PATTERN_REGEX); + Matcher match = pattern.matcher(contentMetadataString); + if (match.find()) { + ArrayList<String> resultList = new ArrayList<>(); + resultList.add(match.group(1)); + mProgramInfo = resultList.get(0); + mLanguage = resultList.get(1); + + if (DEBUG) { + Log.d(TAG, "Converted contentMetadataString result: " + match.group()); + } + + return new BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo(mProgramInfo) + .setLanguage(mLanguage) + .build(); + } else { + if (DEBUG) { + Log.d(TAG, + "The match fail, can not convert it to BluetoothLeAudioContentMetadata."); + } + return null; + } + } + + private BluetoothLeBroadcastChannel convertToChannel(String channelString, + BluetoothLeAudioCodecConfigMetadata configMetadata) { + if (DEBUG) { + Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel"); + } + Pattern pattern = Pattern.compile(PATTERN_REGEX); + Matcher match = pattern.matcher(channelString); + if (match.find()) { + ArrayList<String> resultList = new ArrayList<>(); + resultList.add(match.group(1)); + mIsSelected = Boolean.valueOf(resultList.get(0)); + mChannelIndex = Integer.parseInt(resultList.get(1)); + + if (DEBUG) { + Log.d(TAG, "Converted channelString result: " + match.group()); + } + + return new BluetoothLeBroadcastChannel.Builder() + .setSelected(mIsSelected) + .setChannelIndex(mChannelIndex) + .setCodecMetadata(configMetadata) + .build(); + } else { + if (DEBUG) { + Log.d(TAG, + "The match fail, can not convert it to BluetoothLeBroadcastChannel."); + } + return null; + } + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index c7673aa98797..f949f99673d9 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -146,6 +146,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK, Settings.Secure.UI_NIGHT_MODE, + Settings.Secure.UI_NIGHT_MODE_CUSTOM_TYPE, Settings.Secure.DARK_THEME_CUSTOM_START_TIME, Settings.Secure.DARK_THEME_CUSTOM_END_TIME, Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index cbd71c02baf4..2bdf81912709 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -253,6 +253,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ODI_CAPTIONS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DARK_MODE_DIALOG_SEEN, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.UI_NIGHT_MODE, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.UI_NIGHT_MODE_CUSTOM_TYPE, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.DARK_THEME_CUSTOM_START_TIME, NONE_NEGATIVE_LONG_VALIDATOR); VALIDATORS.put(Secure.DARK_THEME_CUSTOM_END_TIME, NONE_NEGATIVE_LONG_VALIDATOR); VALIDATORS.put(Secure.GLOBAL_ACTIONS_PANEL_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index aa6661b2514c..fd7554f11873 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -618,7 +618,7 @@ final class SettingsState { return; } HistoricalOperation operation = new HistoricalOperation( - SystemClock.elapsedRealtime(), type, + System.currentTimeMillis(), type, setting != null ? new Setting(setting) : null); if (mNextHistoricalOpIdx >= mHistoricalOperations.size()) { mHistoricalOperations.add(operation); diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index 5593fdfe5f51..c480197d23dc 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -29,7 +29,7 @@ import android.view.animation.Interpolator * A class that allows changes in bounds within a view hierarchy to animate seamlessly between the * start and end state. */ -class ViewBoundAnimator { +class ViewHierarchyAnimator { // TODO(b/221418522): make this private once it can't be passed as an arg anymore. enum class Bound(val label: String, val overrideTag: Int) { LEFT("left", R.id.tag_override_left) { diff --git a/packages/SystemUI/docs/media-controls.md b/packages/SystemUI/docs/media-controls.md index 579f453a3a92..112e216dc8f4 100644 --- a/packages/SystemUI/docs/media-controls.md +++ b/packages/SystemUI/docs/media-controls.md @@ -41,7 +41,7 @@ Files under [`systemui/media/`](/packages/SystemUI/src/com/android/systemui/medi * SeekBarViewModel.kt * Implements its own `computePosition()` for the seekbar (to avoid continually polling the `PlaybackState`, which involves binder calls) * Does some touch falsing (ignore flings, require drags to start near the thumb - otherwise users would often accidentally trigger the seekbar when they meant to move the carousel or shade) - * PlayerViewHolder.kt + * MediaViewHolder.kt * Holds references to the UI elements in the panel * Animation support: * MediaHierarchyManager.kt diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index d8b050aa8e2c..46dad02ddb45 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -31,37 +31,134 @@ const val ACCENT1_CHROMA = 48.0f const val GOOGLE_BLUE = 0xFF1b6ef3.toInt() const val MIN_CHROMA = 5 -internal enum class ChromaStrategy { - EQ, GTE +internal interface Hue { + fun get(sourceColor: Cam): Double + + /** + * Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the + * hue fall betweens, and use the hue rotation of the lower hue. + * + * @param sourceHue hue of source color + * @param hueAndRotations list of pairs, where the first item in a pair is a hue, and the + * second item in the pair is a hue rotation that should be applied + */ + fun getHueRotation(sourceHue: Float, hueAndRotations: List<Pair<Int, Int>>): Double { + for (i in 0..hueAndRotations.size) { + val previousIndex = if (i == 0) hueAndRotations.size - 1 else i - 1 + val thisHue = hueAndRotations[i].first + val previousHue = hueAndRotations[previousIndex].first + if (ColorScheme.angleIsBetween(sourceHue, thisHue, previousHue)) { + return ColorScheme.wrapDegreesDouble(sourceHue.toDouble() + + hueAndRotations[previousIndex].first) + } + } + + // If this statement executes, something is wrong, there should have been a rotation + // found using the arrays. + return sourceHue.toDouble() + } } -internal enum class HueStrategy { - SOURCE, ADD, SUBTRACT +internal class HueSource : Hue { + override fun get(sourceColor: Cam): Double { + return sourceColor.hue.toDouble() + } } -internal class Chroma(val strategy: ChromaStrategy, val value: Double) { - fun get(sourceChroma: Double): Double { - return when (strategy) { - ChromaStrategy.EQ -> value - ChromaStrategy.GTE -> sourceChroma.coerceAtLeast(value) - } +internal class HueAdd(val amountDegrees: Double) : Hue { + override fun get(sourceColor: Cam): Double { + return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() + amountDegrees) + } +} + +internal class HueSubtract(val amountDegrees: Double) : Hue { + override fun get(sourceColor: Cam): Double { + return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() - amountDegrees) + } +} + +internal class HueVibrantSecondary() : Hue { + val hueToRotations = listOf(Pair(24, 15), Pair(53, 15), Pair(91, 15), Pair(123, 15), + Pair(141, 15), Pair(172, 15), Pair(198, 15), Pair(234, 18), Pair(272, 18), + Pair(302, 18), Pair(329, 30), Pair(354, 15)) + override fun get(sourceColor: Cam): Double { + return getHueRotation(sourceColor.hue, hueToRotations) + } +} + +internal class HueVibrantTertiary() : Hue { + val hueToRotations = listOf(Pair(24, 30), Pair(53, 30), Pair(91, 15), Pair(123, 30), + Pair(141, 27), Pair(172, 27), Pair(198, 30), Pair(234, 35), Pair(272, 30), + Pair(302, 30), Pair(329, 60), Pair(354, 30)) + override fun get(sourceColor: Cam): Double { + return getHueRotation(sourceColor.hue, hueToRotations) + } +} + +internal class HueExpressiveSecondary() : Hue { + val hueToRotations = listOf(Pair(24, 95), Pair(53, 45), Pair(91, 45), Pair(123, 20), + Pair(141, 45), Pair(172, 45), Pair(198, 15), Pair(234, 15), + Pair(272, 45), Pair(302, 45), Pair(329, 45), Pair(354, 45)) + override fun get(sourceColor: Cam): Double { + return getHueRotation(sourceColor.hue, hueToRotations) + } +} + +internal class HueExpressiveTertiary() : Hue { + val hueToRotations = listOf(Pair(24, 20), Pair(53, 20), Pair(91, 20), Pair(123, 45), + Pair(141, 20), Pair(172, 20), Pair(198, 90), Pair(234, 90), Pair(272, 20), + Pair(302, 20), Pair(329, 120), Pair(354, 120)) + override fun get(sourceColor: Cam): Double { + return getHueRotation(sourceColor.hue, hueToRotations) } } -internal class Hue(val strategy: HueStrategy = HueStrategy.SOURCE, val value: Double = 0.0) { - fun get(sourceHue: Double): Double { - return when (strategy) { - HueStrategy.SOURCE -> sourceHue - HueStrategy.ADD -> ColorScheme.wrapDegreesDouble(sourceHue + value) - HueStrategy.SUBTRACT -> ColorScheme.wrapDegreesDouble(sourceHue - value) +internal interface Chroma { + fun get(sourceColor: Cam): Double + + /** + * Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the + * hue fall betweens, and use the hue rotation of the lower hue. + * + * @param sourceHue hue of source color + * @param hueAndChromas list of pairs, where the first item in a pair is a hue, and the + * second item in the pair is a chroma that should be applied + */ + fun getSpecifiedChroma(sourceHue: Float, hueAndChromas: List<Pair<Int, Int>>): Double { + for (i in 0..hueAndChromas.size) { + val previousIndex = if (i == 0) hueAndChromas.size - 1 else i - 1 + val thisHue = hueAndChromas[i].first + val previousHue = hueAndChromas[previousIndex].first + if (ColorScheme.angleIsBetween(sourceHue, thisHue, previousHue)) { + return hueAndChromas[i].second.toDouble() + } } + + // If this statement executes, something is wrong, there should have been a rotation + // found using the arrays. + return sourceHue.toDouble() + } +} + +internal class ChromaConstant(val chroma: Double) : Chroma { + override fun get(sourceColor: Cam): Double { + return chroma + } +} + +internal class ChromaExpressiveNeutral() : Chroma { + val hueToChromas = listOf(Pair(24, 8), Pair(53, 8), Pair(91, 8), Pair(123, 8), + Pair(141, 6), Pair(172, 6), Pair(198, 8), Pair(234, 8), Pair(272, 8), + Pair(302, 8), Pair(329, 8), Pair(354, 8)) + override fun get(sourceColor: Cam): Double { + return getSpecifiedChroma(sourceColor.hue, hueToChromas) } } -internal class TonalSpec(val hue: Hue = Hue(), val chroma: Chroma) { +internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) { fun shades(sourceColor: Cam): List<Int> { - val hue = hue.get(sourceColor.hue.toDouble()) - val chroma = chroma.get(sourceColor.chroma.toDouble()) + val hue = hue.get(sourceColor) + val chroma = chroma.get(sourceColor) return Shades.of(hue.toFloat(), chroma.toFloat()).toList() } } @@ -76,46 +173,46 @@ internal class CoreSpec( enum class Style(internal val coreSpec: CoreSpec) { SPRITZ(CoreSpec( - a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)), - a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)), - a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), - n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)) + a1 = TonalSpec(HueSource(), ChromaConstant(12.0)), + a2 = TonalSpec(HueSource(), ChromaConstant(8.0)), + a3 = TonalSpec(HueSource(), ChromaConstant(16.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(2.0)), + n2 = TonalSpec(HueSource(), ChromaConstant(2.0)) )), TONAL_SPOT(CoreSpec( - a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 32.0)), - a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), - a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)), - n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)) + a1 = TonalSpec(HueSource(), ChromaConstant(36.0)), + a2 = TonalSpec(HueSource(), ChromaConstant(16.0)), + a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(4.0)), + n2 = TonalSpec(HueSource(), ChromaConstant(8.0)) )), VIBRANT(CoreSpec( - a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), - a2 = TonalSpec(Hue(HueStrategy.ADD, 15.0), Chroma(ChromaStrategy.EQ, 24.0)), - a3 = TonalSpec(Hue(HueStrategy.ADD, 30.0), Chroma(ChromaStrategy.GTE, 32.0)), - n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)) + a1 = TonalSpec(HueSource(), ChromaConstant(48.0)), + a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)), + a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(6.0)), + n2 = TonalSpec(HueSource(), ChromaConstant(12.0)) )), EXPRESSIVE(CoreSpec( - a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 60.0), Chroma(ChromaStrategy.GTE, 64.0)), - a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 30.0), Chroma(ChromaStrategy.EQ, 24.0)), - a3 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), - n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)) + a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)), + a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)), + a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(40.0)), + n1 = TonalSpec(HueAdd(15.0), ChromaExpressiveNeutral()), + n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0)) )), RAINBOW(CoreSpec( - a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), - a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), - a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)), - n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0)) + a1 = TonalSpec(HueSource(), ChromaConstant(48.0)), + a2 = TonalSpec(HueSource(), ChromaConstant(16.0)), + a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(0.0)), + n2 = TonalSpec(HueSource(), ChromaConstant(0.0)) )), FRUIT_SALAD(CoreSpec( - a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.GTE, 48.0)), - a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.EQ, 36.0)), - a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 36.0)), - n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 10.0)), - n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)) + a1 = TonalSpec(HueSubtract(50.0), ChromaConstant(48.0)), + a2 = TonalSpec(HueSubtract(50.0), ChromaConstant(36.0)), + a3 = TonalSpec(HueSource(), ChromaConstant(36.0)), + n1 = TonalSpec(HueSource(), ChromaConstant(10.0)), + n2 = TonalSpec(HueSource(), ChromaConstant(16.0)) )), } @@ -296,6 +393,13 @@ class ColorScheme( return seeds } + internal fun angleIsBetween(angle: Float, a: Int, b: Int): Boolean { + if (a < b) { + return a <= angle && angle <= b + } + return a <= angle || angle <= b + } + private fun wrapDegrees(degrees: Int): Int { return when { degrees < 0 -> { diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java index aab3538e3c4c..c97b9601f743 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java @@ -59,9 +59,6 @@ public class Shades { shades[1] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 95); for (int i = 2; i < 12; i++) { float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1); - if (lStar >= 90) { - chroma = Math.min(40f, chroma); - } shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar); } return shades; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 6d088f090bcd..1fec3314a13e 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -17,6 +17,7 @@ package com.android.systemui.plugins; import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Intent; +import android.os.UserHandle; import android.view.View; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -70,6 +71,9 @@ public interface ActivityStarter { void startActivity(Intent intent, boolean dismissShade, @Nullable ActivityLaunchAnimator.Controller animationController, boolean showOverLockscreenWhenLocked); + void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController, + boolean showOverLockscreenWhenLocked, UserHandle userHandle); void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade); void startActivity(Intent intent, boolean dismissShade, Callback callback); void postStartActivityDismissingKeyguard(Intent intent, int delay); diff --git a/packages/SystemUI/res/drawable/new_fgs_dot.xml b/packages/SystemUI/res/drawable/new_fgs_dot.xml index 759ddaf1e73f..3669e1d3c374 100644 --- a/packages/SystemUI/res/drawable/new_fgs_dot.xml +++ b/packages/SystemUI/res/drawable/new_fgs_dot.xml @@ -15,8 +15,9 @@ ** limitations under the License. --> <shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="oval" android:width="12dp" android:height="12dp"> - <solid android:color="@*android:color/red" /> + <solid android:color="?androidprv:attr/colorAccentTertiary" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_media_button_background.xml b/packages/SystemUI/res/drawable/qs_media_outline_button.xml index ed9bd263a79d..ed9bd263a79d 100644 --- a/packages/SystemUI/res/drawable/qs_media_button_background.xml +++ b/packages/SystemUI/res/drawable/qs_media_outline_button.xml diff --git a/packages/SystemUI/res/drawable/qs_media_solid_button.xml b/packages/SystemUI/res/drawable/qs_media_solid_button.xml new file mode 100644 index 000000000000..baa4aaee7031 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_media_solid_button.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorAccentPrimaryVariant" /> + <corners android:radius="24dp"/> + <padding + android:left="16dp" + android:right="16dp" + android:top="8dp" + android:bottom="8dp" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index 20747fad021b..d39b0d53c743 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -33,7 +33,7 @@ android:layout_height="match_parent" android:background="@drawable/media_output_item_background" android:layout_gravity="center_vertical|start"> - <SeekBar + <com.android.systemui.media.dialog.MediaOutputSeekbar android:id="@+id/volume_seekbar" android:splitTrack="false" android:visibility="gone" @@ -127,6 +127,7 @@ android:layout_gravity="right|center" android:button="@drawable/ic_circle_check_box" android:visibility="gone" + android:clickable="false" /> </FrameLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index f030f3130be4..6cf32151d8ea 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -264,7 +264,7 @@ <TextView android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/qs_media_padding" + android:layout_marginTop="0dp" android:layout_marginStart="@dimen/qs_media_padding" android:layout_marginEnd="@dimen/qs_media_padding" android:id="@+id/remove_text" @@ -274,64 +274,56 @@ android:marqueeRepeatLimit="marquee_forever" android:text="@string/controls_media_close_session" android:gravity="center_horizontal|top" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toBottomOf="@id/settings" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/cancel" /> - <FrameLayout + <ImageButton android:id="@+id/settings" - android:background="@drawable/qs_media_light_source" + android:src="@drawable/ic_settings" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="@dimen/qs_media_padding" - app:layout_constrainedWidth="true" + android:layout_marginTop="4dp" + android:layout_marginEnd="4dp" + android:background="@drawable/qs_media_light_source" + android:contentDescription="@string/controls_media_settings_button" + android:layout_gravity="top" app:layout_constraintWidth_min="@dimen/min_clickable_item_size" app:layout_constraintHeight_min="@dimen/min_clickable_item_size" - app:layout_constraintHorizontal_chainStyle="spread_inside" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/cancel" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/remove_text"> - <TextView - android:id="@+id/settings_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center|bottom" - style="@style/MediaPlayer.OutlineButton" - android:text="@string/controls_media_settings_button" /> - </FrameLayout> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent"> + </ImageButton> <FrameLayout - android:id="@+id/cancel" + android:id="@+id/dismiss" android:background="@drawable/qs_media_light_source" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/qs_media_action_spacing" + android:layout_marginStart="@dimen/qs_media_padding" android:layout_marginEnd="@dimen/qs_media_action_spacing" android:layout_marginBottom="@dimen/qs_media_padding" app:layout_constrainedWidth="true" app:layout_constraintWidth_min="@dimen/min_clickable_item_size" app:layout_constraintHeight_min="@dimen/min_clickable_item_size" - app:layout_constraintStart_toEndOf="@id/settings" - app:layout_constraintEnd_toStartOf="@id/dismiss" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/cancel" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/remove_text"> <TextView - android:id="@+id/cancel_text" + android:id="@+id/dismiss_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center|bottom" - style="@style/MediaPlayer.OutlineButton" - android:text="@string/cancel" /> + android:layout_gravity="center|top" + style="@style/MediaPlayer.SolidButton" + android:background="@drawable/qs_media_solid_button" + android:text="@string/controls_media_dismiss_button" /> </FrameLayout> - <FrameLayout - android:id="@+id/dismiss" + android:id="@+id/cancel" android:background="@drawable/qs_media_light_source" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginEnd="@dimen/qs_media_padding" @@ -339,16 +331,16 @@ app:layout_constrainedWidth="true" app:layout_constraintWidth_min="@dimen/min_clickable_item_size" app:layout_constraintHeight_min="@dimen/min_clickable_item_size" - app:layout_constraintStart_toEndOf="@id/cancel" + app:layout_constraintStart_toEndOf="@id/dismiss" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/remove_text"> <TextView - android:id="@+id/dismiss_text" + android:id="@+id/cancel_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center|bottom" + android:layout_gravity="center|top" style="@style/MediaPlayer.OutlineButton" - android:text="@string/controls_media_dismiss_button" /> + android:text="@string/cancel" /> </FrameLayout> </com.android.systemui.util.animation.TransitionLayout> diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml deleted file mode 100644 index 9471b9f9786c..000000000000 --- a/packages/SystemUI/res/layout/media_view.xml +++ /dev/null @@ -1,300 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2019 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<!-- Layout for media controls inside QSPanel carousel --> -<com.android.systemui.util.animation.TransitionLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/qs_media_controls" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:clipToPadding="false" - android:gravity="center_horizontal|fill_vertical" - android:forceHasOverlappingRendering="false" - android:background="@drawable/qs_media_background" - android:theme="@style/MediaPlayer"> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/center_vertical_guideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintGuide_percent="0.6" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/center_horizontal_guideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - app:layout_constraintGuide_begin="48dp" /> - - <!-- As per Material Design on Biderectionality, this is forced to LTR in code --> - <FrameLayout - android:id="@+id/notification_media_progress_time" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:forceHasOverlappingRendering="false"> - <!-- width is set to "match_parent" to avoid extra layout calls --> - <TextView - android:id="@+id/media_elapsed_time" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:gravity="start" - android:textSize="12sp" /> - - <TextView - android:id="@+id/media_total_time" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:gravity="end" - android:textSize="12sp" /> - </FrameLayout> - - <!-- Actions must be ordered left-to-right even in RTL layout. However, they appear in a chain - with the artist name, and must as a group appear at the end of that chain. This is - accomplished by having all actions appear in a LTR chain within the parent, and then biasing it - to the right side, then this barrier is used to bound the text views. --> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/media_action_barrier" - android:layout_width="0dp" - android:layout_height="0dp" - android:orientation="vertical" - app:layout_constraintTop_toBottomOf="@id/header_title" - app:barrierDirection="start" - app:constraint_referenced_ids="action0,action1,action2,action3,action4" - /> - - <ImageButton - android:id="@+id/action0" - style="@style/MediaPlayer.Action" - android:layout_width="48dp" - android:layout_height="48dp" /> - - <ImageButton - android:id="@+id/action1" - style="@style/MediaPlayer.Action" - android:layout_width="48dp" - android:layout_height="48dp" /> - - <ImageButton - android:id="@+id/action2" - style="@style/MediaPlayer.Action" - android:layout_width="48dp" - android:layout_height="48dp" /> - - <ImageButton - android:id="@+id/action3" - style="@style/MediaPlayer.Action" - android:layout_width="48dp" - android:layout_height="48dp" /> - - <ImageButton - android:id="@+id/action4" - style="@style/MediaPlayer.Action" - android:layout_width="48dp" - android:layout_height="48dp" /> - - <!-- Album Art --> - <ImageView - android:id="@+id/album_art" - android:layout_width="@dimen/qs_media_album_size" - android:layout_height="@dimen/qs_media_album_size" - android:layout_gravity="center_vertical" - style="@style/MediaPlayer.Album" - android:background="@drawable/qs_media_art_background" - android:clipToOutline="true" /> - - <!-- Seamless Output Switcher --> - <LinearLayout - android:id="@+id/media_seamless" - android:layout_width="0dp" - android:layout_height="48dp" - android:orientation="horizontal" - android:gravity="top|end" - android:paddingTop="@dimen/qs_media_padding" - android:paddingEnd="@dimen/qs_media_padding" - android:background="@drawable/qs_media_light_source" - android:forceHasOverlappingRendering="false"> - <LinearLayout - android:id="@+id/media_seamless_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minHeight="@dimen/qs_seamless_height" - android:theme="@style/MediaPlayer.SolidButton" - android:background="@drawable/qs_media_seamless_background" - android:orientation="horizontal" - android:contentDescription="@string/quick_settings_media_device_label"> - <ImageView - android:id="@+id/media_seamless_image" - android:layout_width="@dimen/qs_seamless_icon_size" - android:layout_height="@dimen/qs_seamless_icon_size" - android:layout_gravity="center" - android:tint="?android:attr/textColorPrimary" - android:src="@*android:drawable/ic_media_seamless" /> - <TextView - android:id="@+id/media_seamless_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginStart="4dp" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:singleLine="true" - android:text="@*android:string/ext_media_seamless_action" - android:textDirection="locale" - android:textSize="12sp" - android:lineHeight="16sp" /> - </LinearLayout> - </LinearLayout> - - <!-- Seek Bar --> - <!-- As per Material Design on Biderectionality, this is forced to LTR in code --> - <SeekBar - android:id="@+id/media_progress_bar" - style="@style/MediaPlayer.ProgressBar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:maxHeight="@dimen/qs_media_enabled_seekbar_height" - android:paddingTop="@dimen/qs_media_enabled_seekbar_vertical_padding" - android:layout_marginTop="-22dp" - android:paddingBottom="2dp" - android:splitTrack="false" /> - - <!-- Song name --> - <TextView - android:id="@+id/header_title" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:singleLine="true" - android:textSize="16sp" /> - - <!-- Artist name --> - <TextView - android:id="@+id/header_artist" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:singleLine="true" - style="@style/MediaPlayer.Subtitle" - android:textSize="14sp" /> - - <com.android.internal.widget.CachingIconView - android:id="@+id/icon" - style="@style/MediaPlayer.AppIcon" - android:layout_width="@dimen/qs_media_icon_size" - android:layout_height="@dimen/qs_media_icon_size" /> - - <!-- Long press menu --> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/qs_media_padding" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginEnd="@dimen/qs_media_padding" - android:id="@+id/remove_text" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:singleLine="true" - android:ellipsize="marquee" - android:marqueeRepeatLimit="marquee_forever" - android:text="@string/controls_media_close_session" - android:gravity="center_horizontal|top" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toTopOf="@id/cancel"/> - - <FrameLayout - android:id="@+id/settings" - android:background="@drawable/qs_media_light_source" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="@dimen/qs_media_padding" - app:layout_constrainedWidth="true" - app:layout_constraintWidth_min="48dp" - app:layout_constraintHeight_min="48dp" - app:layout_constraintHorizontal_chainStyle="spread_inside" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/cancel" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/remove_text"> - - <TextView - android:id="@+id/settings_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center|bottom" - style="@style/MediaPlayer.OutlineButton" - android:text="@string/controls_media_settings_button" /> - </FrameLayout> - - <FrameLayout - android:id="@+id/cancel" - android:background="@drawable/qs_media_light_source" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="@dimen/qs_media_padding" - app:layout_constrainedWidth="true" - app:layout_constraintWidth_min="48dp" - app:layout_constraintHeight_min="48dp" - app:layout_constraintStart_toEndOf="@id/settings" - app:layout_constraintEnd_toStartOf="@id/dismiss" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/remove_text"> - - <TextView - android:id="@+id/cancel_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center|bottom" - style="@style/MediaPlayer.OutlineButton" - android:text="@string/cancel" /> - </FrameLayout> - - <FrameLayout - android:id="@+id/dismiss" - android:background="@drawable/qs_media_light_source" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_padding" - android:layout_marginBottom="@dimen/qs_media_padding" - app:layout_constrainedWidth="true" - app:layout_constraintWidth_min="48dp" - app:layout_constraintHeight_min="48dp" - app:layout_constraintStart_toEndOf="@id/cancel" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/remove_text"> - - <TextView - android:id="@+id/dismiss_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center|bottom" - style="@style/MediaPlayer.OutlineButton" - android:text="@string/controls_media_dismiss_button" - /> - </FrameLayout> -</com.android.systemui.util.animation.TransitionLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml deleted file mode 100644 index bb6d4bddf25a..000000000000 --- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** Copyright 2020, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. ---> -<com.android.systemui.RegionInterceptingFrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/rounded_corners_bottom" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <ImageView - android:id="@+id/left" - android:layout_width="12dp" - android:layout_height="12dp" - android:layout_gravity="left|bottom" - android:tint="#ff000000" - android:visibility="gone" - android:src="@drawable/rounded_corner_bottom"/> - - <ImageView - android:id="@+id/right" - android:layout_width="12dp" - android:layout_height="12dp" - android:tint="#ff000000" - android:visibility="gone" - android:layout_gravity="right|bottom" - android:src="@drawable/rounded_corner_bottom"/> - -</com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml deleted file mode 100644 index 46648c88d921..000000000000 --- a/packages/SystemUI/res/layout/rounded_corners_top.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** Copyright 2020, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. ---> -<com.android.systemui.RegionInterceptingFrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/rounded_corners_top" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <ImageView - android:id="@+id/left" - android:layout_width="12dp" - android:layout_height="12dp" - android:layout_gravity="left|top" - android:tint="#ff000000" - android:visibility="gone" - android:src="@drawable/rounded_corner_top"/> - - <ImageView - android:id="@+id/right" - android:layout_width="12dp" - android:layout_height="12dp" - android:tint="#ff000000" - android:visibility="gone" - android:layout_gravity="right|top" - android:src="@drawable/rounded_corner_top"/> - -</com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 8f4e11527d95..73457262fd17 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -959,10 +959,7 @@ <!-- Size of media cards in the QSPanel carousel --> <dimen name="qs_media_padding">16dp</dimen> - <dimen name="qs_media_album_size_small">72dp</dimen> - <dimen name="qs_media_album_size">84dp</dimen> <dimen name="qs_media_album_radius">14dp</dimen> - <dimen name="qs_media_album_device_padding">26dp</dimen> <dimen name="qs_media_info_margin">12dp</dimen> <dimen name="qs_media_info_spacing">8dp</dimen> <dimen name="qs_media_icon_size">20dp</dimen> @@ -974,10 +971,7 @@ <dimen name="qs_seamless_icon_size">12dp</dimen> <dimen name="qs_media_disabled_seekbar_height">1dp</dimen> <dimen name="qs_media_enabled_seekbar_height">2dp</dimen> - <dimen name="qs_media_enabled_seekbar_vertical_padding">28dp</dimen> - <dimen name="qs_media_disabled_seekbar_vertical_padding">29dp</dimen> - <!-- Sizes for alternate session-based layout --> <dimen name="qs_media_session_enabled_seekbar_vertical_padding">15dp</dimen> <dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen> <dimen name="qs_media_session_height_expanded">184dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 926734c2749f..096b9a0c4790 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -167,5 +167,11 @@ <item type="id" name="action_move_bottom_right"/> <item type="id" name="action_move_to_edge_and_hide"/> <item type="id" name="action_move_out_edge_and_show"/> + + <!-- rounded corner view id --> + <item type="id" name="rounded_corner_top_left"/> + <item type="id" name="rounded_corner_top_right"/> + <item type="id" name="rounded_corner_bottom_left"/> + <item type="id" name="rounded_corner_bottom_right"/> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ee4b9a13058e..d3b76d90d09f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2116,11 +2116,11 @@ <!-- Title for media controls [CHAR_LIMIT=50] --> <string name="controls_media_title">Media</string> <!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=50] --> - <string name="controls_media_close_session">Hide this media session?</string> + <string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="YouTube Music">%1$s</xliff:g>?</string> <!-- Explanation that controls associated with a specific media session are active [CHAR_LIMIT=50] --> <string name="controls_media_active_session">The current media session cannot be hidden.</string> <!-- Label for a button that will hide media controls [CHAR_LIMIT=30] --> - <string name="controls_media_dismiss_button">Dismiss</string> + <string name="controls_media_dismiss_button">Hide</string> <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] --> <string name="controls_media_resume">Resume</string> <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index f5c1382b9ae9..a61eda8f0e09 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -630,7 +630,7 @@ </style> <style name="MediaPlayer.OutlineButton"> - <item name="android:background">@drawable/qs_media_button_background</item> + <item name="android:background">@drawable/qs_media_outline_button</item> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:backgroundTint">@color/media_player_outline_button_bg</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml deleted file mode 100644 index 12e446f53634..000000000000 --- a/packages/SystemUI/res/xml/media_collapsed.xml +++ /dev/null @@ -1,176 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> -<ConstraintSet - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - <Constraint - android:id="@+id/icon" - android:layout_width="@dimen/qs_media_icon_size" - android:layout_height="@dimen/qs_media_icon_size" - android:translationY="@dimen/qs_media_icon_offset" - android:translationX="@dimen/qs_media_icon_offset" - app:layout_constraintEnd_toEndOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" - /> - - <Constraint - android:id="@+id/media_seamless" - android:layout_width="wrap_content" - android:layout_height="48dp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toEndOf="@id/center_vertical_guideline" - app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline" - app:layout_constraintHorizontal_chainStyle="spread_inside" - app:layout_constraintHorizontal_bias="1" - app:layout_constrainedWidth="true" - app:layout_constraintWidth_min="48dp" - app:layout_constraintHeight_min="48dp" - android:layout_marginStart="@dimen/qs_center_guideline_padding" - /> - - <Constraint - android:id="@+id/album_art" - android:layout_width="@dimen/qs_media_album_size_small" - android:layout_height="@dimen/qs_media_album_size_small" - android:layout_marginTop="@dimen/qs_media_padding" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginBottom="@dimen/qs_media_padding" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - /> - - <!-- Song name --> - <Constraint - android:id="@+id/header_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/qs_media_info_margin" - android:layout_marginEnd="@dimen/qs_center_guideline_padding" - app:layout_constrainedWidth="true" - app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline" - app:layout_constraintStart_toEndOf="@id/album_art" - app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline" - app:layout_constraintHorizontal_bias="0"/> - - <!-- Artist name --> - <Constraint - android:id="@+id/header_artist" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constrainedWidth="true" - android:layout_marginTop="@dimen/qs_media_info_spacing" - app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline" - app:layout_constraintStart_toStartOf="@id/header_title" - app:layout_constraintEnd_toStartOf="@id/media_action_barrier" - app:layout_constraintHorizontal_bias="0"/> - - <!-- Seek Bar --> - <Constraint - android:id="@+id/media_progress_bar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:alpha="0.0" - app:layout_constraintTop_toBottomOf="@id/album_art" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - android:visibility="gone" - /> - - <Constraint - android:id="@+id/notification_media_progress_time" - android:alpha="0.0" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintTop_toBottomOf="@id/album_art" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - android:visibility="gone" - /> - - <Constraint - android:id="@+id/action0" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:visibility="gone" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toLeftOf="@id/action1" - app:layout_constraintHorizontal_bias="1" - > - </Constraint> - - <Constraint - android:id="@+id/action1" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toRightOf="@id/action0" - app:layout_constraintRight_toLeftOf="@id/action2" - > - </Constraint> - - <Constraint - android:id="@+id/action2" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toRightOf="@id/action1" - app:layout_constraintRight_toLeftOf="@id/action3" - > - </Constraint> - - <Constraint - android:id="@+id/action3" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toRightOf="@id/action2" - app:layout_constraintRight_toLeftOf="@id/action4" - > - </Constraint> - - <Constraint - android:id="@+id/action4" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_padding" - android:visibility="gone" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toRightOf="@id/action3" - app:layout_constraintRight_toRightOf="parent" - app:layout_constraintHorizontal_bias="0" - > - </Constraint> -</ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml deleted file mode 100644 index 6b83aae38e15..000000000000 --- a/packages/SystemUI/res/xml/media_expanded.xml +++ /dev/null @@ -1,174 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> -<ConstraintSet - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - <Constraint - android:id="@+id/icon" - android:layout_width="@dimen/qs_media_icon_size" - android:layout_height="@dimen/qs_media_icon_size" - android:translationY="@dimen/qs_media_icon_offset" - android:translationX="@dimen/qs_media_icon_offset" - app:layout_constraintEnd_toEndOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" - /> - - <Constraint - android:id="@+id/media_seamless" - android:layout_width="wrap_content" - android:layout_height="48dp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toEndOf="@id/center_vertical_guideline" - app:layout_constraintHorizontal_chainStyle="spread_inside" - app:layout_constraintHorizontal_bias="1" - app:layout_constrainedWidth="true" - app:layout_constraintWidth_min="48dp" - app:layout_constraintHeight_min="48dp" - android:paddingTop="@dimen/qs_media_padding" - android:paddingEnd="@dimen/qs_media_padding" - android:layout_marginStart="@dimen/qs_center_guideline_padding" - android:layout_marginBottom="4dp" /> - - <Constraint - android:id="@+id/album_art" - android:layout_width="@dimen/qs_media_album_size" - android:layout_height="@dimen/qs_media_album_size" - android:layout_marginTop="@dimen/qs_media_padding" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginBottom="0dp" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" - /> - - <!-- Song name --> - <Constraint - android:id="@+id/header_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="26dp" - android:layout_marginStart="@dimen/qs_media_info_margin" - android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constrainedWidth="true" - app:layout_constraintTop_toTopOf="@+id/album_art" - app:layout_constraintStart_toEndOf="@id/album_art" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0"/> - - <!-- Artist name --> - <Constraint - android:id="@+id/header_artist" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/qs_media_padding" - android:layout_marginBottom="@dimen/qs_media_info_margin" - app:layout_constrainedWidth="true" - android:layout_marginTop="1dp" - app:layout_constraintTop_toBottomOf="@id/header_title" - app:layout_constraintStart_toStartOf="@id/header_title" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0"/> - - <!-- Seek Bar --> - <Constraint - android:id="@+id/media_progress_bar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="34dp" - app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - /> - - <Constraint - android:id="@+id/notification_media_progress_time" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/qs_media_padding" - android:layout_marginStart="@dimen/qs_media_padding" - app:layout_constraintTop_toBottomOf="@id/media_progress_bar" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - /> - - <Constraint - android:id="@+id/action0" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="@dimen/qs_media_action_margin" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toLeftOf="@id/action1" - app:layout_constraintTop_toBottomOf="@id/media_progress_bar" - app:layout_constraintBottom_toBottomOf="parent"> - </Constraint> - - <Constraint - android:id="@+id/action1" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="@dimen/qs_media_action_margin" - app:layout_constraintLeft_toRightOf="@id/action0" - app:layout_constraintRight_toLeftOf="@id/action2" - app:layout_constraintTop_toBottomOf="@id/media_progress_bar" - app:layout_constraintBottom_toBottomOf="parent"> - </Constraint> - - <Constraint - android:id="@+id/action2" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="@dimen/qs_media_action_margin" - app:layout_constraintLeft_toRightOf="@id/action1" - app:layout_constraintRight_toLeftOf="@id/action3" - app:layout_constraintTop_toBottomOf="@id/media_progress_bar" - app:layout_constraintBottom_toBottomOf="parent"> - </Constraint> - - <Constraint - android:id="@+id/action3" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="@dimen/qs_media_action_margin" - app:layout_constraintLeft_toRightOf="@id/action2" - app:layout_constraintRight_toLeftOf="@id/action4" - app:layout_constraintTop_toBottomOf="@id/media_progress_bar" - app:layout_constraintBottom_toBottomOf="parent"> - </Constraint> - - <Constraint - android:id="@+id/action4" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginStart="@dimen/qs_media_action_spacing" - android:layout_marginEnd="@dimen/qs_media_padding" - android:layout_marginBottom="@dimen/qs_media_action_margin" - app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintLeft_toRightOf="@id/action3" - app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toBottomOf="@id/media_progress_bar" - app:layout_constraintBottom_toBottomOf="parent"> - </Constraint> -</ConstraintSet> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index 75579b05aeeb..5ab2fd0aff09 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -57,7 +57,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp private ColorStateList mDefaultColorState; private CharSequence mMessage; private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR); - private boolean mBouncerVisible; + private boolean mBouncerShowing; private boolean mAltBouncerShowing; /** * Container that wraps the KeyguardMessageArea - may be null if current view hierarchy doesn't @@ -177,7 +177,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp void update() { CharSequence status = mMessage; - setVisibility(TextUtils.isEmpty(status) || (!mBouncerVisible && !mAltBouncerShowing) + setVisibility(TextUtils.isEmpty(status) || (!mBouncerShowing && !mAltBouncerShowing) ? INVISIBLE : VISIBLE); setText(status); ColorStateList colorState = mDefaultColorState; @@ -192,8 +192,14 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp setTextColor(colorState); } - public void setBouncerVisible(boolean bouncerVisible) { - mBouncerVisible = bouncerVisible; + /** + * Set whether the bouncer is fully showing + */ + public void setBouncerShowing(boolean bouncerShowing) { + if (mBouncerShowing != bouncerShowing) { + mBouncerShowing = bouncerShowing; + update(); + } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index 05318bb0df78..81cc3a933600 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -39,12 +39,6 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag public void onStartedWakingUp() { mView.setSelected(true); } - - @Override - public void onKeyguardBouncerChanged(boolean bouncer) { - mView.setBouncerVisible(bouncer); - mView.update(); - } }; private ConfigurationListener mConfigurationListener = new ConfigurationListener() { @@ -94,6 +88,13 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag mView.setAltBouncerShowing(showing); } + /** + * Set bouncer is fully showing + */ + public void setBouncerShowing(boolean showing) { + mView.setBouncerShowing(showing); + } + public void setMessage(CharSequence s) { mView.setMessage(s); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3858f9cd7a82..5602f3d3801b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1486,6 +1486,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationFailed() { handleFingerprintAuthFailed(); + + // TODO(b/225231929): Refactor as needed, add tests, etc. + mTrustManager.reportUserRequestedUnlock( + KeyguardUpdateMonitor.getCurrentUser(), true); } @Override @@ -1497,17 +1501,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationHelp"); handleFingerprintHelp(helpMsgId, helpString.toString()); + Trace.endSection(); } @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { + Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationError"); handleFingerprintError(errMsgId, errString.toString()); + Trace.endSection(); } @Override public void onAuthenticationAcquired(int acquireInfo) { + Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired"); handleFingerprintAcquired(acquireInfo); + Trace.endSection(); } @Override @@ -2258,7 +2268,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (shouldTriggerActiveUnlock()) { - mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser()); + // TODO(b/225231929): Refactor surrounding code to reflect calling of new method + mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser()); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index e36e984380e2..a4a0105fae3d 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -212,19 +212,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme updateBurnInOffsets(); updateVisibility(); + mAccessibilityManager.addTouchExplorationStateChangeListener( + mTouchExplorationStateChangeListener); updateAccessibility(); } private void updateAccessibility() { if (mAccessibilityManager.isTouchExplorationEnabled()) { - mView.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - onLongPress(); - } - } - ); + mView.setOnClickListener(mA11yClickListener); } else { mView.setOnClickListener(null); } @@ -242,6 +237,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mCancelDelayedUpdateVisibilityRunnable.run(); mCancelDelayedUpdateVisibilityRunnable = null; } + + mAccessibilityManager.removeTouchExplorationStateChangeListener( + mTouchExplorationStateChangeListener); } public float getTop() { @@ -267,7 +265,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return; } - boolean wasShowingUnlock = mShowUnlockIcon; boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon && !mShowAodUnlockedIcon && !mShowAodLockIcon; mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen() @@ -702,6 +699,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setAlpha(alpha); } + private void updateUdfpsConfig() { + // must be called from the main thread since it may update the views + mExecutor.execute(() -> { + updateIsUdfpsEnrolled(); + updateConfiguration(); + }); + } + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override public void onAllAuthenticatorsRegistered() { @@ -714,11 +719,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } }; - private void updateUdfpsConfig() { - // must be called from the main thread since it may update the views - mExecutor.execute(() -> { - updateIsUdfpsEnrolled(); - updateConfiguration(); - }); - } + private final View.OnClickListener mA11yClickListener = v -> onLongPress(); + + private final AccessibilityManager.TouchExplorationStateChangeListener + mTouchExplorationStateChangeListener = enabled -> updateAccessibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java index 5bd620e873b0..7af6f6677f3f 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java @@ -16,6 +16,7 @@ package com.android.systemui; import android.app.PendingIntent; import android.content.Intent; +import android.os.UserHandle; import android.view.View; import androidx.annotation.Nullable; @@ -100,6 +101,15 @@ public class ActivityStarterDelegate implements ActivityStarter { } @Override + public void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController, + boolean showOverLockscreenWhenLocked, UserHandle userHandle) { + mActualStarterOptionalLazy.get().ifPresent( + starter -> starter.startActivity(intent, dismissShade, animationController, + showOverLockscreenWhenLocked, userHandle)); + } + + @Override public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) { mActualStarterOptionalLazy.get().ifPresent( starter -> starter.startActivity(intent, onlyProvisioned, dismissShade)); diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index 56046d9c057d..ccb5b1146a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -79,6 +79,8 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { override fun onAttachedToWindow() { super.onAttachedToWindow() updateCutout() + updateProtectionBoundingPath() + onUpdate() } fun onDisplayChanged(displayId: Int) { @@ -93,6 +95,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { if (displayId == display.displayId) { updateCutout() updateProtectionBoundingPath() + onUpdate() } } @@ -100,8 +103,13 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { displayRotation = rotation updateCutout() updateProtectionBoundingPath() + onUpdate() } + // Called after the cutout and protection bounding path change. Subclasses + // should make any changes that need to happen based on the change. + open fun onUpdate() = Unit + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public override fun onDraw(canvas: Canvas) { super.onDraw(canvas) diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java index b5b6b1340108..9a6020f8556b 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java @@ -128,7 +128,7 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver { UserSwitcherController userSwitcherController, UiEventLogger uiEventLogger, int userId) { - super(context); + super(context, false /* dismissOnDeviceLock */); setTitle(context.getString(R.string.guest_wipe_session_title)); setMessage(context.getString(R.string.guest_wipe_session_message)); diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt index 498e71546bed..011881354e35 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -101,10 +101,10 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec override fun onAttachedToWindow() { super.onAttachedToWindow() + parent.requestTransparentRegion(this) if (!DEBUG_COLOR) { - parent.requestTransparentRegion(this) + viewRootImpl.setDisplayDecoration(true) } - viewRootImpl.setDisplayDecoration(true) if (useInvertedAlphaColor) { paint.set(clearPaint) @@ -114,6 +114,10 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec } } + override fun onUpdate() { + parent.requestTransparentRegion(this) + } + override fun onDraw(canvas: Canvas) { // If updating onDraw, also update gatherTransparentRegion if (useInvertedAlphaColor) { @@ -128,7 +132,6 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec super.onDraw(canvas) debugTransparentRegionPaint?.let { - calculateTransparentRect() canvas.drawRect(transparentRect, it) } } @@ -136,7 +139,16 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec override fun gatherTransparentRegion(region: Region?): Boolean { region?.let { calculateTransparentRect() - region.op(transparentRect, Region.Op.INTERSECT) + if (DEBUG_COLOR) { + // Since we're going to draw a rectangle where the layer would + // normally be transparent, treat the transparent region as + // empty. We still want this method to be called, though, so + // that it calculates the transparent rect at the right time + // to match !DEBUG_COLOR. + region.setEmpty() + } else { + region.op(transparentRect, Region.Op.INTERSECT) + } } // Always return false - views underneath this should always be visible. return false @@ -357,10 +369,15 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec * Update the rounded corner size. */ fun updateRoundedCornerSize(top: Int, bottom: Int) { + if (roundedCornerTopSize == top && roundedCornerBottomSize == bottom) { + return + } roundedCornerTopSize = top roundedCornerBottomSize = bottom updateRoundedCornerDrawableBounds() - invalidate() + + // Use requestLayout() to trigger transparent region recalculated + requestLayout() } private fun updateRoundedCornerDrawableBounds() { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 2f5292cec909..5de09b13bf6a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -77,6 +77,7 @@ import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; import com.android.systemui.decor.OverlayWindow; import com.android.systemui.decor.PrivacyDotDecorProviderFactory; +import com.android.systemui.decor.RoundedCornerDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegate; import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.UserTracker; @@ -138,6 +139,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @VisibleForTesting protected RoundedCornerResDelegate mRoundedCornerResDelegate; @VisibleForTesting + protected DecorProviderFactory mRoundedCornerFactory; + private int mProviderRefreshToken = 0; + @VisibleForTesting protected OverlayWindow[] mOverlays = null; @Nullable private DisplayCutoutView[] mCutoutViews; @@ -292,11 +296,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mDisplayUniqueId = mContext.getDisplay().getUniqueId(); mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(), mDisplayUniqueId); + mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate); mWindowManager = mContext.getSystemService(WindowManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); - updateRoundedCornerDrawable(); - updateRoundedCornerRadii(); + updateHwLayerRoundedCornerDrawable(); setupDecorations(); setupCameraListener(); @@ -348,7 +352,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab final String newUniqueId = mContext.getDisplay().getUniqueId(); if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { mDisplayUniqueId = newUniqueId; - mRoundedCornerResDelegate.reloadAll(newUniqueId); + mRoundedCornerResDelegate.updateDisplayUniqueId(newUniqueId, null); final DisplayDecorationSupport newScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); // When the value of mSupportHwcScreenDecoration is changed, re-setup the whole @@ -359,12 +363,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab setupDecorations(); return; } - updateRoundedCornerDrawable(); + updateHwLayerRoundedCornerDrawable(); } if (mScreenDecorHwcLayer != null) { mScreenDecorHwcLayer.onDisplayChanged(displayId); } - updateOrientation(); + updateView(); } }; @@ -406,22 +410,22 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private void setupDecorations() { - List<DecorProvider> decorProviders = mDotFactory.getProviders(); - - if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) { + if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()) { + List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders()); if (mHwcScreenDecorationSupport != null) { createHwcOverlay(); } else { removeHwcOverlay(); + decorProviders.addAll(mRoundedCornerFactory.getProviders()); } final DisplayCutout cutout = getCutout(); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout) - || shouldShowPrivacyDot(i, cutout)) { + if (shouldShowSwLayerCutout(i, cutout) || shouldShowSwLayerRoundedCorner(i, cutout) + || shouldShowSwLayerPrivacyDot(i, cutout)) { Pair<List<DecorProvider>, List<DecorProvider>> pair = DecorProviderKt.partitionAlignedBound(decorProviders, i); decorProviders = pair.getSecond(); - createOverlay(i, cutout, pair.getFirst()); + createOverlay(i, pair.getFirst()); } else { removeOverlay(i); } @@ -522,7 +526,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private void createOverlay( @BoundsPosition int pos, - @Nullable DisplayCutout cutout, @NonNull List<DecorProvider> decorProviders) { if (mOverlays == null) { mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH]; @@ -547,7 +550,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mCutoutViews[pos] = new DisplayCutoutView(mContext, pos); mCutoutViews[pos].setColor(mTintColor); overlayView.addView(mCutoutViews[pos]); - updateView(pos, cutout); + mCutoutViews[pos].updateRotation(mRotation); } mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); @@ -603,7 +606,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private OverlayWindow overlayForPosition( @BoundsPosition int pos, @NonNull List<DecorProvider> decorProviders) { - final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos); + final OverlayWindow currentOverlay = new OverlayWindow(mContext); decorProviders.forEach(provider -> { removeOverlayView(provider.getViewId()); currentOverlay.addDecorProvider(provider, mRotation); @@ -617,22 +620,16 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return currentOverlay; } - private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { - if (mOverlays == null || mOverlays[pos] == null || mHwcScreenDecorationSupport != null) { + private void updateView() { + if (mOverlays == null) { return; } - - // update rounded corner view rotation - updateRoundedCornerView(pos, R.id.left, cutout); - updateRoundedCornerView(pos, R.id.right, cutout); - updateRoundedCornerSize( - mRoundedCornerResDelegate.getTopRoundedSize(), - mRoundedCornerResDelegate.getBottomRoundedSize()); - updateRoundedCornerImageView(); - - // update cutout view rotation - if (mCutoutViews != null && mCutoutViews[pos] != null) { - mCutoutViews[pos].updateRotation(mRotation); + ++mProviderRefreshToken; + for (final OverlayWindow overlay: mOverlays) { + if (overlay == null) { + continue; + } + overlay.onReloadResAndMeasure(null, mProviderRefreshToken, mRotation, mDisplayUniqueId); } } @@ -806,7 +803,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab int oldRotation = mRotation; mPendingRotationChange = false; updateOrientation(); - updateRoundedCornerRadii(); if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation); setupDecorations(); if (mOverlays != null) { @@ -866,109 +862,32 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mDotViewController.setNewRotation(newRotation); } - if (mPendingRotationChange) { - return; - } - if (newRotation != mRotation) { + if (!mPendingRotationChange && newRotation != mRotation) { mRotation = newRotation; if (mScreenDecorHwcLayer != null) { mScreenDecorHwcLayer.pendingRotationChange = false; mScreenDecorHwcLayer.updateRotation(mRotation); + updateHwLayerRoundedCornerSize(); + updateHwLayerRoundedCornerDrawable(); } - if (mOverlays != null) { - updateLayoutParams(); - final DisplayCutout cutout = getCutout(); - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] == null) { + updateLayoutParams(); + // update cutout view rotation + if (mCutoutViews != null) { + for (final DisplayCutoutView cutoutView: mCutoutViews) { + if (cutoutView == null) { continue; } - updateView(i, cutout); + cutoutView.updateRotation(mRotation); } } } - } - - private void updateRoundedCornerRadii() { - // We should eventually move to just using the intrinsic size of the drawables since - // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not - // upgrading all of the configs to contain (width, height) pairs. Instead assume that a - // device configured using the single integer config value is okay with drawing the corners - // as a square - final Size oldRoundedDefaultTop = mRoundedCornerResDelegate.getTopRoundedSize(); - final Size oldRoundedDefaultBottom = mRoundedCornerResDelegate.getBottomRoundedSize(); - mRoundedCornerResDelegate.reloadAll(mDisplayUniqueId); - final Size newRoundedDefaultTop = mRoundedCornerResDelegate.getTopRoundedSize(); - final Size newRoundedDefaultBottom = mRoundedCornerResDelegate.getBottomRoundedSize(); - - if (oldRoundedDefaultTop.getWidth() != newRoundedDefaultTop.getWidth() - || oldRoundedDefaultBottom.getWidth() != newRoundedDefaultBottom.getWidth()) { - onTuningChanged(SIZE, null); - } - } - - private void updateRoundedCornerView(@BoundsPosition int pos, int id, - @Nullable DisplayCutout cutout) { - final View rounded = mOverlays[pos].getRootView().findViewById(id); - if (rounded == null) { - return; - } - rounded.setVisibility(View.GONE); - if (shouldShowRoundedCorner(pos, cutout)) { - final int gravity = getRoundedCornerGravity(pos, id == R.id.left); - ((FrameLayout.LayoutParams) rounded.getLayoutParams()).gravity = gravity; - setRoundedCornerOrientation(rounded, gravity); - rounded.setVisibility(View.VISIBLE); - } - } - private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) { - final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); - switch (rotatedPos) { - case BOUNDS_POSITION_LEFT: - return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.BOTTOM | Gravity.LEFT; - case BOUNDS_POSITION_TOP: - return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.TOP | Gravity.RIGHT; - case BOUNDS_POSITION_RIGHT: - return isStart ? Gravity.TOP | Gravity.RIGHT : Gravity.BOTTOM | Gravity.RIGHT; - case BOUNDS_POSITION_BOTTOM: - return isStart ? Gravity.BOTTOM | Gravity.LEFT : Gravity.BOTTOM | Gravity.RIGHT; - default: - throw new IllegalArgumentException("Incorrect position: " + rotatedPos); - } + // update views + updateView(); } - /** - * Configures the rounded corner drawable's view matrix based on the gravity. - * - * The gravity describes which corner to configure for, and the drawable we are rotating is - * assumed to be oriented for the top-left corner of the device regardless of the target corner. - * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or - * y-axis for the top-right and bottom-left corners. - */ - private void setRoundedCornerOrientation(View corner, int gravity) { - corner.setRotation(0); - corner.setScaleX(1); - corner.setScaleY(1); - switch (gravity) { - case Gravity.TOP | Gravity.LEFT: - return; - case Gravity.TOP | Gravity.RIGHT: - corner.setScaleX(-1); // flip X axis - return; - case Gravity.BOTTOM | Gravity.LEFT: - corner.setScaleY(-1); // flip Y axis - return; - case Gravity.BOTTOM | Gravity.RIGHT: - corner.setRotation(180); - return; - default: - throw new IllegalArgumentException("Unsupported gravity: " + gravity); - } - } private boolean hasRoundedCorners() { - return mRoundedCornerResDelegate.getBottomRoundedSize().getWidth() > 0 - || mRoundedCornerResDelegate.getTopRoundedSize().getWidth() > 0 - || mRoundedCornerResDelegate.isMultipleRadius(); + return mRoundedCornerFactory.getHasProviders(); } private boolean isDefaultShownOverlayPos(@BoundsPosition int pos, @@ -987,17 +906,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } - private boolean shouldShowRoundedCorner(@BoundsPosition int pos, + private boolean shouldShowSwLayerRoundedCorner(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout) && mHwcScreenDecorationSupport == null; } - private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { + private boolean shouldShowSwLayerPrivacyDot(@BoundsPosition int pos, + @Nullable DisplayCutout cutout) { return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout); } - private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { + private boolean shouldShowSwLayerCutout(@BoundsPosition int pos, + @Nullable DisplayCutout cutout) { final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); return (bounds != null && !bounds[rotatedPos].isEmpty() @@ -1032,54 +953,33 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } mExecutor.execute(() -> { - if (mOverlays == null) return; - if (SIZE.equals(key)) { - if (newValue != null) { - try { - mRoundedCornerResDelegate.updateTuningSizeFactor( - Integer.parseInt(newValue)); - } catch (Exception e) { - } + if (mOverlays == null || !SIZE.equals(key)) { + return; + } + ++mProviderRefreshToken; + try { + final int sizeFactor = Integer.parseInt(newValue); + mRoundedCornerResDelegate.updateTuningSizeFactor(sizeFactor, mProviderRefreshToken); + } catch (NumberFormatException e) { + mRoundedCornerResDelegate.updateTuningSizeFactor(null, mProviderRefreshToken); + } + Integer[] filterIds = { + R.id.rounded_corner_top_left, + R.id.rounded_corner_top_right, + R.id.rounded_corner_bottom_left, + R.id.rounded_corner_bottom_right + }; + for (final OverlayWindow overlay: mOverlays) { + if (overlay == null) { + continue; } - updateRoundedCornerSize( - mRoundedCornerResDelegate.getTopRoundedSize(), - mRoundedCornerResDelegate.getBottomRoundedSize()); + overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation, + mDisplayUniqueId); } + updateHwLayerRoundedCornerSize(); }); } - private void updateRoundedCornerDrawable() { - mRoundedCornerResDelegate.reloadAll(mDisplayUniqueId); - updateRoundedCornerImageView(); - } - - private void updateRoundedCornerImageView() { - final Drawable top = mRoundedCornerResDelegate.getTopRoundedDrawable(); - final Drawable bottom = mRoundedCornerResDelegate.getBottomRoundedDrawable(); - - if (mScreenDecorHwcLayer != null) { - mScreenDecorHwcLayer.updateRoundedCornerDrawable(top, bottom); - return; - } - - if (mOverlays == null) { - return; - } - final ColorStateList colorStateList = ColorStateList.valueOf(mTintColor); - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] == null) { - continue; - } - final ViewGroup overlayView = mOverlays[i].getRootView(); - ((ImageView) overlayView.findViewById(R.id.left)).setImageTintList(colorStateList); - ((ImageView) overlayView.findViewById(R.id.right)).setImageTintList(colorStateList); - ((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable( - isTopRoundedCorner(i, R.id.left) ? top : bottom); - ((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable( - isTopRoundedCorner(i, R.id.right) ? top : bottom); - } - } - private void updateHwLayerRoundedCornerDrawable() { if (mScreenDecorHwcLayer == null) { return; @@ -1094,25 +994,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable); } - @VisibleForTesting - boolean isTopRoundedCorner(@BoundsPosition int pos, int id) { - switch (pos) { - case BOUNDS_POSITION_LEFT: - case BOUNDS_POSITION_RIGHT: - if (mRotation == ROTATION_270) { - return id == R.id.left ? false : true; - } else { - return id == R.id.left ? true : false; - } - case BOUNDS_POSITION_TOP: - return true; - case BOUNDS_POSITION_BOTTOM: - return false; - default: - throw new IllegalArgumentException("Unknown bounds position"); - } - } - private void updateHwLayerRoundedCornerSize() { if (mScreenDecorHwcLayer == null) { return; @@ -1124,28 +1005,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mScreenDecorHwcLayer.updateRoundedCornerSize(topWidth, bottomWidth); } - private void updateRoundedCornerSize(Size sizeTop, Size sizeBottom) { - - if (mScreenDecorHwcLayer != null) { - mScreenDecorHwcLayer.updateRoundedCornerSize(sizeTop.getWidth(), sizeBottom.getWidth()); - return; - } - - if (mOverlays == null) { - return; - } - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] == null) { - continue; - } - final ViewGroup overlayView = mOverlays[i].getRootView(); - setSize(overlayView.findViewById(R.id.left), - isTopRoundedCorner(i, R.id.left) ? sizeTop : sizeBottom); - setSize(overlayView.findViewById(R.id.right), - isTopRoundedCorner(i, R.id.right) ? sizeTop : sizeBottom); - } - } - @VisibleForTesting protected void setSize(View view, Size pixelSize) { LayoutParams params = view.getLayoutParams(); diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 46a03e809b06..49f758405fbd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -77,8 +77,8 @@ class ControlsActivity @Inject constructor( initBroadcastReceiver() } - override fun onResume() { - super.onResume() + override fun onStart() { + super.onStart() parent = requireViewById<ViewGroup>(R.id.global_actions_controls) parent.alpha = 0f @@ -91,8 +91,8 @@ class ControlsActivity @Inject constructor( finish() } - override fun onPause() { - super.onPause() + override fun onStop() { + super.onStop() uiController.hide() } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 166c2654cbaa..59fcf87c5029 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -43,6 +43,7 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.lowlightclock.LowLightClockController; import com.android.systemui.model.SysUiState; +import com.android.systemui.navigationbar.NavigationBarComponent; import com.android.systemui.plugins.BcSmartspaceDataPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; @@ -132,6 +133,7 @@ import dagger.Provides; }, subcomponents = { CentralSurfacesComponent.class, + NavigationBarComponent.class, NotificationRowComponent.class, DozeComponent.class, ExpandableNotificationRowComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt index 3543bb4ab9e9..03ee8b11ab41 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.decor +import android.content.Context import android.view.DisplayCutout -import android.view.LayoutInflater import android.view.Surface import android.view.View import android.view.ViewGroup @@ -38,9 +38,20 @@ abstract class DecorProvider { /** The aligned bounds for the view which is created through inflateView() */ abstract val alignedBounds: List<Int> + /** + * Called when res info changed. + * Child provider needs to implement it if its view needs to be updated. + */ + abstract fun onReloadResAndMeasure( + view: View, + reloadToken: Int, + @Surface.Rotation rotation: Int, + displayUniqueId: String? = null + ) + /** Inflate view into parent as current rotation */ abstract fun inflateView( - inflater: LayoutInflater, + context: Context, parent: ViewGroup, @Surface.Rotation rotation: Int ): View diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt index 9f8679cdea4a..f38ff14726ca 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt @@ -16,31 +16,22 @@ package com.android.systemui.decor import android.annotation.IdRes -import android.view.DisplayCutout -import android.view.LayoutInflater +import android.content.Context import android.view.Surface import android.view.View import android.view.ViewGroup -import com.android.systemui.R -import java.util.HashMap +import com.android.systemui.RegionInterceptingFrameLayout -class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: Int) { +class OverlayWindow(private val context: Context) { - private val layoutId: Int - get() { - return if (pos == DisplayCutout.BOUNDS_POSITION_LEFT || - pos == DisplayCutout.BOUNDS_POSITION_TOP) { - R.layout.rounded_corners_top - } else { - R.layout.rounded_corners_bottom - } - } - - val rootView = layoutInflater.inflate(layoutId, null) as ViewGroup - private val viewProviderMap: MutableMap<Int, Pair<View, DecorProvider>> = HashMap() + val rootView = RegionInterceptingFrameLayout(context) as ViewGroup + private val viewProviderMap = mutableMapOf<Int, Pair<View, DecorProvider>>() - fun addDecorProvider(decorProvider: DecorProvider, @Surface.Rotation rotation: Int) { - val view = decorProvider.inflateView(layoutInflater, rootView, rotation) + fun addDecorProvider( + decorProvider: DecorProvider, + @Surface.Rotation rotation: Int + ) { + val view = decorProvider.inflateView(context, rootView, rotation) viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider) } @@ -56,4 +47,35 @@ class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: viewProviderMap.remove(id) } } + + /** + * Apply new configuration info into views. + * @param filterIds target view ids. Apply to all if null. + * @param rotation current or new rotation direction. + * @param displayUniqueId new displayUniqueId if any. + */ + fun onReloadResAndMeasure( + filterIds: Array<Int>? = null, + reloadToken: Int, + @Surface.Rotation rotation: Int, + displayUniqueId: String? = null + ) { + filterIds?.forEach { id -> + viewProviderMap[id]?.let { + it.second.onReloadResAndMeasure( + view = it.first, + reloadToken = reloadToken, + displayUniqueId = displayUniqueId, + rotation = rotation) + } + } ?: run { + viewProviderMap.values.forEach { + it.second.onReloadResAndMeasure( + view = it.first, + reloadToken = reloadToken, + displayUniqueId = displayUniqueId, + rotation = rotation) + } + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt index 7afd7e0eedc5..136f135af759 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt @@ -16,6 +16,7 @@ package com.android.systemui.decor +import android.content.Context import android.content.res.Resources import android.view.DisplayCutout import android.view.LayoutInflater @@ -76,12 +77,21 @@ class PrivacyDotCornerDecorProviderImpl( private val layoutId: Int ) : CornerDecorProvider() { + override fun onReloadResAndMeasure( + view: View, + reloadToken: Int, + rotation: Int, + displayUniqueId: String? + ) { + // Do nothing here because it is handled inside PrivacyDotViewController + } + override fun inflateView( - inflater: LayoutInflater, + context: Context, parent: ViewGroup, @Surface.Rotation rotation: Int ): View { - inflater.inflate(layoutId, parent, true) + LayoutInflater.from(context).inflate(layoutId, parent, true) return parent.getChildAt(parent.childCount - 1 /* latest new added child */) } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt new file mode 100644 index 000000000000..4388b8b3b92d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.view.DisplayCutout +import com.android.systemui.R + +class RoundedCornerDecorProviderFactory( + private val roundedCornerResDelegate: RoundedCornerResDelegate +) : DecorProviderFactory() { + + override val hasProviders: Boolean + get() = roundedCornerResDelegate.run { + // We don't consider isMultipleRadius here because it makes no sense if size is zero. + topRoundedSize.width > 0 || bottomRoundedSize.width > 0 + } + + override val providers: List<DecorProvider> + get() { + val hasTop = roundedCornerResDelegate.topRoundedSize.width > 0 + val hasBottom = roundedCornerResDelegate.bottomRoundedSize.width > 0 + return when { + hasTop && hasBottom -> listOf( + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_top_left, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, + roundedCornerResDelegate = roundedCornerResDelegate), + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_top_right, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, + roundedCornerResDelegate = roundedCornerResDelegate), + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_bottom_left, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, + roundedCornerResDelegate = roundedCornerResDelegate), + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_bottom_right, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, + roundedCornerResDelegate = roundedCornerResDelegate) + ) + hasTop -> listOf( + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_top_left, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, + roundedCornerResDelegate = roundedCornerResDelegate), + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_top_right, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, + roundedCornerResDelegate = roundedCornerResDelegate) + ) + hasBottom -> listOf( + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_bottom_left, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, + roundedCornerResDelegate = roundedCornerResDelegate), + RoundedCornerDecorProviderImpl( + viewId = R.id.rounded_corner_bottom_right, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, + roundedCornerResDelegate = roundedCornerResDelegate) + ) + else -> emptyList() + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt new file mode 100644 index 000000000000..90ff950406b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.content.Context +import android.view.DisplayCutout +import android.view.Gravity +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import com.android.systemui.R + +class RoundedCornerDecorProviderImpl( + override val viewId: Int, + @DisplayCutout.BoundsPosition override val alignedBound1: Int, + @DisplayCutout.BoundsPosition override val alignedBound2: Int, + private val roundedCornerResDelegate: RoundedCornerResDelegate +) : CornerDecorProvider() { + + private val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + + override fun inflateView( + context: Context, + parent: ViewGroup, + @Surface.Rotation rotation: Int + ): View { + return ImageView(context).also { view -> + // View + view.id = viewId + initView(view, rotation) + + // LayoutParams + val layoutSize = if (isTop) { + roundedCornerResDelegate.topRoundedSize + } else { + roundedCornerResDelegate.bottomRoundedSize + } + val params = FrameLayout.LayoutParams( + layoutSize.width, + layoutSize.height, + alignedBound1.toLayoutGravity(rotation) or + alignedBound2.toLayoutGravity(rotation)) + + // AddView + parent.addView(view, params) + } + } + + private fun initView(view: ImageView, @Surface.Rotation rotation: Int) { + view.setRoundedCornerImage(roundedCornerResDelegate, isTop) + view.adjustRotation(alignedBounds, rotation) + view.setColorFilter(IMAGE_TINT_COLOR) + } + + override fun onReloadResAndMeasure( + view: View, + reloadToken: Int, + @Surface.Rotation rotation: Int, + displayUniqueId: String? + ) { + roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, reloadToken) + + initView((view as ImageView), rotation) + + val layoutSize = if (isTop) { + roundedCornerResDelegate.topRoundedSize + } else { + roundedCornerResDelegate.bottomRoundedSize + } + (view.layoutParams as FrameLayout.LayoutParams).let { + it.width = layoutSize.width + it.height = layoutSize.height + it.gravity = alignedBound1.toLayoutGravity(rotation) or + alignedBound2.toLayoutGravity(rotation) + view.setLayoutParams(it) + } + } +} + +private const val IMAGE_TINT_COLOR: Int = 0xFF000000.toInt() + +@DisplayCutout.BoundsPosition +private fun Int.toLayoutGravity(@Surface.Rotation rotation: Int): Int = when (rotation) { + Surface.ROTATION_0 -> when (this) { + DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.LEFT + DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.TOP + DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.RIGHT + else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.BOTTOM + } + Surface.ROTATION_90 -> when (this) { + DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.BOTTOM + DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.LEFT + DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.TOP + else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT + } + Surface.ROTATION_270 -> when (this) { + DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.TOP + DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.RIGHT + DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.BOTTOM + else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT + } + else /* Surface.ROTATION_180 */ -> when (this) { + DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.RIGHT + DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.BOTTOM + DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.LEFT + else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.TOP + } +} + +private fun ImageView.setRoundedCornerImage( + resDelegate: RoundedCornerResDelegate, + isTop: Boolean +) { + val drawable = if (isTop) + resDelegate.topRoundedDrawable + else + resDelegate.bottomRoundedDrawable + + if (drawable != null) { + setImageDrawable(drawable) + } else { + setImageResource( + if (isTop) + R.drawable.rounded_corner_top + else + R.drawable.rounded_corner_bottom + ) + } +} + +/** + * Configures the rounded corner drawable's view matrix based on the gravity. + * + * The gravity describes which corner to configure for, and the drawable we are rotating is assumed + * to be oriented for the top-left corner of the device regardless of the target corner. + * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or + * y-axis for the top-right and bottom-left corners. + */ +private fun ImageView.adjustRotation(alignedBounds: List<Int>, @Surface.Rotation rotation: Int) { + var newRotation = 0F + var newScaleX = 1F + var newScaleY = 1F + + val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + val isLeft = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT) + when (rotation) { + Surface.ROTATION_0 -> when { + isTop && isLeft -> {} + isTop && !isLeft -> { newScaleX = -1F } + !isTop && isLeft -> { newScaleY = -1F } + else /* !isTop && !isLeft */ -> { newRotation = 180F } + } + Surface.ROTATION_90 -> when { + isTop && isLeft -> { newScaleY = -1F } + isTop && !isLeft -> {} + !isTop && isLeft -> { newRotation = 180F } + else /* !isTop && !isLeft */ -> { newScaleX = -1F } + } + Surface.ROTATION_270 -> when { + isTop && isLeft -> { newScaleX = -1F } + isTop && !isLeft -> { newRotation = 180F } + !isTop && isLeft -> {} + else /* !isTop && !isLeft */ -> { newScaleY = -1F } + } + else /* Surface.ROTATION_180 */ -> when { + isTop && isLeft -> { newRotation = 180F } + isTop && !isLeft -> { newScaleY = -1F } + !isTop && isLeft -> { newScaleX = -1F } + else /* !isTop && !isLeft */ -> {} + } + } + + this.rotation = newRotation + this.scaleX = newScaleX + this.scaleY = newScaleY +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index c817f89c7a9b..1d38e58dd48c 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -36,6 +36,8 @@ class RoundedCornerResDelegate( private val density: Float get() = res.displayMetrics.density + private var reloadToken: Int = 0 + var isMultipleRadius: Boolean = false private set @@ -60,12 +62,26 @@ class RoundedCornerResDelegate( reloadMeasures() } - fun reloadAll(newDisplayUniqueId: String?) { - displayUniqueId = newDisplayUniqueId + private fun reloadAll(newReloadToken: Int) { + if (reloadToken == newReloadToken) { + return + } + reloadToken = newReloadToken reloadDrawables() reloadMeasures() } + fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) { + if (displayUniqueId != newDisplayUniqueId) { + displayUniqueId = newDisplayUniqueId + newReloadToken ?.let { reloadToken = it } + reloadDrawables() + reloadMeasures() + } else { + newReloadToken?.let { reloadAll(it) } + } + } + private fun reloadDrawables() { val configIdx = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId) isMultipleRadius = getIsMultipleRadius(configIdx) @@ -85,34 +101,6 @@ class RoundedCornerResDelegate( arrayResId = R.array.config_roundedCornerBottomDrawableArray, backupDrawableId = R.drawable.rounded_corner_bottom ) ?: roundedDrawable - - // If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the - // (width, height) size of drawable/rounded.xml instead of rounded_corner_radius - if (isMultipleRadius) { - roundedSize = Size( - roundedDrawable?.intrinsicWidth ?: 0, - roundedDrawable?.intrinsicHeight ?: 0) - topRoundedDrawable?.let { - topRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight) - } - bottomRoundedDrawable?.let { - bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight) - } - } else { - val defaultRadius = RoundedCorners.getRoundedCornerRadius(res, displayUniqueId) - val topRadius = RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId) - val bottomRadius = RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId) - roundedSize = Size(defaultRadius, defaultRadius) - topRoundedSize = Size(topRadius, topRadius) - bottomRoundedSize = Size(bottomRadius, bottomRadius) - } - - if (topRoundedSize.width == 0) { - topRoundedSize = roundedSize - } - if (bottomRoundedSize.width == 0) { - bottomRoundedSize = roundedSize - } } private fun reloadMeasures(roundedSizeFactor: Int? = null) { @@ -137,20 +125,25 @@ class RoundedCornerResDelegate( bottomRoundedSize = Size(bottomRadius, bottomRadius) } - roundedSizeFactor ?.let { - val length: Int = (it * density).toInt() - roundedSize = Size(length, length) - } - if (topRoundedSize.width == 0) { topRoundedSize = roundedSize } if (bottomRoundedSize.width == 0) { bottomRoundedSize = roundedSize } + + if (roundedSizeFactor != null && roundedSizeFactor > 0) { + val length: Int = (roundedSizeFactor * density).toInt() + topRoundedSize = Size(length, length) + bottomRoundedSize = Size(length, length) + } } - fun updateTuningSizeFactor(factor: Int) { + fun updateTuningSizeFactor(factor: Int?, newReloadToken: Int) { + if (reloadToken == newReloadToken) { + return + } + reloadToken = newReloadToken reloadMeasures(factor) } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 0a2e69f943c6..8e1d645d1e97 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -319,9 +319,10 @@ public class DozeLog implements Dumpable { /** * Appends the doze state that was suppressed to the doze event log * @param suppressedState The {@link DozeMachine.State} that was suppressed + * @param reason what suppressed always on */ - public void traceAlwaysOnSuppressed(DozeMachine.State suppressedState) { - mLogger.logAlwaysOnSuppressed(suppressedState); + public void traceAlwaysOnSuppressed(DozeMachine.State suppressedState, String reason) { + mLogger.logAlwaysOnSuppressed(suppressedState, reason); } /** diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index f3f6be210fed..4c81563e4f93 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -267,11 +267,12 @@ class DozeLogger @Inject constructor( }) } - fun logAlwaysOnSuppressed(state: DozeMachine.State) { + fun logAlwaysOnSuppressed(state: DozeMachine.State, reason: String) { buffer.log(TAG, INFO, { str1 = state.name + str2 = reason }, { - "Always-on state suppressed, suppressed state=$str1" + "Always-on state suppressed, suppressed state=$str1 reason=$str2" }) } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index ae01f0ad84c7..5779bb307790 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -358,8 +358,13 @@ public class DozeMachine { return State.FINISH; } if (mDozeHost.isAlwaysOnSuppressed() && requestedState.isAlwaysOn()) { - Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState); - mDozeLog.traceAlwaysOnSuppressed(requestedState); + Log.i(TAG, "Doze is suppressed by an app. Suppressing state: " + requestedState); + mDozeLog.traceAlwaysOnSuppressed(requestedState, "app"); + return State.DOZE; + } + if (mDozeHost.isPowerSaveActive() && requestedState.isAlwaysOn()) { + Log.i(TAG, "Doze is suppressed by battery saver. Suppressing state: " + requestedState); + mDozeLog.traceAlwaysOnSuppressed(requestedState, "batterySaver"); return State.DOZE; } if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 19b0ea1db04e..b06bf89ffdeb 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -136,6 +136,8 @@ public class DozeService extends DreamService @Override public void setDozeScreenState(int state) { super.setDozeScreenState(state); - mDozeMachine.onScreenState(state); + if (mDozeMachine != null) { + mDozeMachine.onScreenState(state); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java index 31d43b5475e0..89f50ad9fc21 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java @@ -166,6 +166,8 @@ public class DozeSuppressor implements DozeMachine.Part { private DozeHost.Callback mHostCallback = new DozeHost.Callback() { @Override public void onPowerSaveChanged(boolean active) { + // handles suppression changes, while DozeMachine#transitionPolicy handles gating + // transitions to DOZE_AOD DozeMachine.State nextState = null; if (mDozeHost.isPowerSaveActive()) { nextState = DozeMachine.State.DOZE; @@ -182,6 +184,8 @@ public class DozeSuppressor implements DozeMachine.Part { @Override public void onAlwaysOnSuppressedChanged(boolean suppressed) { + // handles suppression changes, while DozeMachine#transitionPolicy handles gating + // transitions to DOZE_AOD final DozeMachine.State nextState; if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !suppressed) { nextState = DozeMachine.State.DOZE_AOD; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index dfbb0c7c1624..db225cf498b0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -26,10 +26,13 @@ import android.view.WindowInsets; import android.view.WindowManager; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleRegistry; import androidx.lifecycle.ViewModelStore; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; @@ -61,6 +64,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private final DreamOverlayContainerViewController mDreamOverlayContainerViewController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final DreamPreviewComplication mPreviewComplication; + private final UiEventLogger mUiEventLogger; // A reference to the {@link Window} used to hold the dream overlay. private Window mWindow; @@ -97,6 +101,25 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private DreamOverlayStateController mStateController; + @VisibleForTesting + public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The dream overlay has entered start.") + DREAM_OVERLAY_ENTER_START(989), + @UiEvent(doc = "The dream overlay has completed start.") + DREAM_OVERLAY_COMPLETE_START(990); + + private final int mId; + + DreamOverlayEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + @Inject public DreamOverlayService( Context context, @@ -104,13 +127,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ DreamOverlayComponent.Factory dreamOverlayComponentFactory, DreamOverlayStateController stateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - DreamPreviewComplication previewComplication) { + DreamPreviewComplication previewComplication, + UiEventLogger uiEventLogger) { mContext = context; mExecutor = executor; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); mStateController = stateController; mPreviewComplication = previewComplication; + mUiEventLogger = uiEventLogger; final DreamOverlayComponent component = dreamOverlayComponentFactory.create(mViewModelStore, mHost); @@ -143,6 +168,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ @Override public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { + mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); setCurrentState(Lifecycle.State.STARTED); mExecutor.execute(() -> { if (mDestroyed) { @@ -159,6 +185,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ addOverlayWindowLocked(layoutParams); setCurrentState(Lifecycle.State.RESUMED); mStateController.setOverlayActive(true); + mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START); }); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt index 4e228a14fc88..9b99c522aab9 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt @@ -160,9 +160,8 @@ class DreamsSmartspaceController @Inject constructor( return } - // TODO(b/217559844): Replace with "dream" session when available. val newSession = smartspaceManager.createSmartspaceSession( - SmartspaceConfig.Builder(context, "lockscreen").build()) + SmartspaceConfig.Builder(context, "dream").build()) Log.d(TAG, "Starting smartspace session for dream") newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) this.session = newSession diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java index e140f6b0faa2..b96cee650663 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -31,8 +31,8 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.wm.shell.animation.FlingAnimationUtils; @@ -89,7 +89,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private VelocityTrackerFactory mVelocityTrackerFactory; private final GestureDetector.OnGestureListener mOnGestureListener = - new GestureDetector.SimpleOnGestureListener() { + new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { @@ -134,9 +134,9 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { NotificationShadeWindowController notificationShadeWindowController, ValueAnimatorCreator valueAnimatorCreator, VelocityTrackerFactory velocityTrackerFactory, - @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) - FlingAnimationUtils flingAnimationUtils, @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) + FlingAnimationUtils flingAnimationUtils, + @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) FlingAnimationUtils flingAnimationUtilsClosing, @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) { mDisplayMetrics = displayMetrics; @@ -154,13 +154,16 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { public void getTouchInitiationRegion(Region region) { if (mCentralSurfaces.isBouncerShowing()) { region.op(new Rect(0, 0, mDisplayMetrics.widthPixels, - Math.round(mDisplayMetrics.heightPixels * mBouncerZoneScreenPercentage)), + Math.round( + mDisplayMetrics.heightPixels * mBouncerZoneScreenPercentage)), Region.Op.UNION); } else { region.op(new Rect(0, - Math.round(mDisplayMetrics.heightPixels * (1 - mBouncerZoneScreenPercentage)), - mDisplayMetrics.widthPixels, - mDisplayMetrics.heightPixels), + Math.round( + mDisplayMetrics.heightPixels + * (1 - mBouncerZoneScreenPercentage)), + mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels), Region.Op.UNION); } } @@ -191,7 +194,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { final MotionEvent motionEvent = (MotionEvent) event; - switch(motionEvent.getAction()) { + switch (motionEvent.getAction()) { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mTouchSession.pop(); @@ -210,9 +213,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { final float velocityVector = (float) Math.hypot(horizontalVelocity, verticalVelocity); - final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector) - ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE; + ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE; flingToExpansion(verticalVelocity, expansion); if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { @@ -236,8 +238,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { } protected boolean flingRevealsOverlay(float velocity, float velocityVector) { - // Fully expand if the user has expanded the bouncer less than halfway or final velocity was - // positive, indicating an downward direction. + // Fully expand the space above the bouncer, if the user has expanded the bouncer less + // than halfway or final velocity was positive, indicating a downward direction. if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD; } else { @@ -246,17 +248,20 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { } protected void flingToExpansion(float velocity, float expansion) { + // The animation utils deal in pixel units, rather than expansion height. final float viewHeight = mCentralSurfaces.getDisplayHeight(); final float currentHeight = viewHeight * mCurrentExpansion; final float targetHeight = viewHeight * expansion; final ValueAnimator animator = createExpansionAnimator(expansion); if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { - // The animation utils deal in pixel units, rather than expansion height. - mFlingAnimationUtils.apply(animator, currentHeight, targetHeight, velocity, viewHeight); + // Hides the bouncer, i.e., fully expands the space above the bouncer. + mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity, + viewHeight); } else { - mFlingAnimationUtilsClosing.apply( - animator, mCurrentExpansion, currentHeight, targetHeight, viewHeight); + // Shows the bouncer, i.e., fully collapses the space above the bouncer. + mFlingAnimationUtils.apply( + animator, currentHeight, targetHeight, velocity, viewHeight); } animator.start(); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 61cfe925f640..9356b16806f1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -146,7 +146,6 @@ public class Flags { // 900 - media public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true); public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false); - public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, true); public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true); public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index af553c744311..acb080a2eaaa 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -1067,13 +1067,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene // Add a little delay before executing, to give the dialog a chance to go away before // switching user mHandler.postDelayed(() -> { - try { - int currentUserId = getCurrentUser().id; - mIActivityManager.switchUser(UserHandle.USER_SYSTEM); - mIActivityManager.stopUser(currentUserId, true /*force*/, null); - } catch (RemoteException re) { - Log.e(TAG, "Couldn't logout user " + re); - } + mDevicePolicyManager.logoutUser(); }, mDialogPressDelay); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index bf464ecdec98..d36bb72e4d3b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1238,14 +1238,52 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** - * Locks the keyguard if {@link #mPendingLock} is true, unless we're playing the screen off - * animation. + * Locks the keyguard if {@link #mPendingLock} is true, and there are no reasons to further + * delay the pending lock. * - * If we are, we will lock the keyguard either when the screen off animation ends, or in - * {@link #onStartedWakingUp} if the animation is cancelled. + * If you do delay handling the pending lock, you must ensure that this method is ALWAYS called + * again when the condition causing the delay changes. Otherwise, the device may remain unlocked + * indefinitely. */ public void maybeHandlePendingLock() { - if (mPendingLock && !mScreenOffAnimationController.isKeyguardShowDelayed()) { + if (mPendingLock) { + + // The screen off animation is playing, so if we lock now, the foreground app will + // vanish and the keyguard will jump-cut in. Delay it, until either: + // - The screen off animation ends. We will call maybeHandlePendingLock from + // the end action in UnlockedScreenOffAnimationController#animateInKeyguard. + // - The screen off animation is cancelled by the device waking back up. We will call + // maybeHandlePendingLock from KeyguardViewMediator#onStartedWakingUp. + if (mScreenOffAnimationController.isKeyguardShowDelayed()) { + if (DEBUG) { + Log.d(TAG, "#maybeHandlePendingLock: not handling because the screen off " + + "animation's isKeyguardShowDelayed() returned true. This should be " + + "handled soon by #onStartedWakingUp, or by the end actions of the " + + "screen off animation."); + } + + return; + } + + // The device was re-locked while in the process of unlocking. If we lock now, callbacks + // in the unlock sequence might end up re-unlocking the device. Delay the lock until the + // keyguard is done going away. We'll call maybeHandlePendingLock again in + // StatusBar#finishKeyguardFadingAway, which is always responsible for setting + // isKeyguardGoingAway to false. + if (mKeyguardStateController.isKeyguardGoingAway()) { + if (DEBUG) { + Log.d(TAG, "#maybeHandlePendingLock: not handling because the keyguard is " + + "going away. This should be handled shortly by " + + "StatusBar#finishKeyguardFadingAway."); + } + + return; + } + + if (DEBUG) { + Log.d(TAG, "#maybeHandlePendingLock: handling pending lock; locking keyguard."); + } + doKeyguardLocked(null); mPendingLock = false; } @@ -1669,16 +1707,11 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, return; } - // If the keyguard is already showing, don't bother unless it was in the process of going - // away. If it was going away, keyguard state may be out of sync and we should make sure to - // re-show it explicitly. Check flags in both files to account for the hiding animation - // which results in a delay and discrepancy between flags. - if ((mShowing && mKeyguardViewControllerLazy.get().isShowing()) - && !mKeyguardStateController.isKeyguardGoingAway()) { - if (DEBUG) { - Log.d(TAG, "doKeyguard: not showing " - + "because it is already showing and not going away"); - } + // if the keyguard is already showing, don't bother. check flags in both files + // to account for the hiding animation which results in a delay and discrepancy + // between flags + if (mShowing && mKeyguardViewControllerLazy.get().isShowing()) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); resetStateLocked(); return; } @@ -2185,14 +2218,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mKeyguardExitAnimationRunner = null; mScreenOnCoordinator.setWakeAndUnlocking(false); mPendingLock = false; - - // If we're asked to re-show while the keyguard is going away, force callbacks to ensure - // that state is re-set correctly. Otherwise, we might short circuit since mShowing is - // true during the keyguard going away process, despite having partially set some state - // to unlocked. - setShowingLocked( - true, mKeyguardStateController.isKeyguardGoingAway() /* forceCallbacks */); - + setShowingLocked(true); mKeyguardViewControllerLazy.get().show(options); resetKeyguardDonePendingLocked(); mHideAnimationRun = false; @@ -2347,8 +2373,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report // the next draw from here, so we don't have to wait for window manager to signal // this to our ViewRootImpl. - mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw( - false /* syncBuffer */); + mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw(); mScreenOnCoordinator.setWakeAndUnlocking(false); } @@ -2363,28 +2388,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, @Override public void onAnimationFinished() throws RemoteException { try { - // WindowManager always needs to know that this animation - // finished so it does not wait the 10s until timeout. finishedCallback.onAnimationFinished(); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onAnimationFinished", e); } - - // If we're not interactive, it means the device is going back to - // sleep. This happens if the power button is pressed during the - // activity launch. If we're going back to sleep, we should *not* - // run keyguard exit finished callbacks and hide the keyguard, since - // we are in the process of locking again and this might result in - // the device staying unlocked when it shouldn't. - // We need to directly query isInteractive rather than mGoingToSleep - // because mGoingToSleep is set in onStartedGoingToSleep, which is - // dispatched asynchronously. - if (mPM.isInteractive()) { - onKeyguardExitFinished(); - mKeyguardViewControllerLazy.get().hide(0 /* startTime */, - 0 /* fadeoutDuration */); - mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); - } + onKeyguardExitFinished(); + mKeyguardViewControllerLazy.get().hide(0 /* startTime */, + 0 /* fadeoutDuration */); + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index c3f4ce986596..5a8b7e3d3d64 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -45,8 +45,7 @@ class KeyguardMediaController @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, private val notifLockscreenUserManager: NotificationLockscreenUserManager, private val context: Context, - configurationController: ConfigurationController, - private val mediaFlags: MediaFlags + configurationController: ConfigurationController ) { init { @@ -62,11 +61,7 @@ class KeyguardMediaController @Inject constructor( }) // First let's set the desired state that we want for this host - mediaHost.expansion = if (mediaFlags.useMediaSessionLayout()) { - MediaHostState.EXPANDED - } else { - MediaHostState.COLLAPSED - } + mediaHost.expansion = MediaHostState.EXPANDED mediaHost.showsOnlyActiveMedia = true mediaHost.falsingProtectionNeeded = true diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 83ad027baf28..20029fec4dd1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -419,15 +419,8 @@ class MediaCarouselController @Inject constructor( .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) if (existingPlayer == null) { var newPlayer = mediaControlPanelFactory.get() - if (mediaFlags.useMediaSessionLayout()) { - newPlayer.attachPlayer( - PlayerSessionViewHolder.create(LayoutInflater.from(context), mediaContent), - MediaViewController.TYPE.PLAYER_SESSION) - } else { - newPlayer.attachPlayer( - PlayerViewHolder.create(LayoutInflater.from(context), mediaContent), - MediaViewController.TYPE.PLAYER) - } + newPlayer.attachPlayer(MediaViewHolder.create( + LayoutInflater.from(context), mediaContent)) newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -522,7 +515,7 @@ class MediaCarouselController @Inject constructor( private fun recreatePlayers() { bgColor = getBackgroundColor() - pageIndicator.tintList = ColorStateList.valueOf(getForegroundColor()) + pageIndicator.tintList = ColorStateList.valueOf(R.color.material_dynamic_neutral_variant80) MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) -> if (isSsMediaRec) { @@ -543,14 +536,6 @@ class MediaCarouselController @Inject constructor( return context.getColor(R.color.material_dynamic_secondary95) } - private fun getForegroundColor(): Int { - return if (mediaFlags.useMediaSessionLayout()) { - context.getColor(R.color.material_dynamic_neutral_variant80) - } else { - context.getColor(R.color.material_dynamic_secondary10) - } - } - private fun updatePageIndicator() { val numPages = mediaContent.getChildCount() pageIndicator.setNumPages(numPages) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index ffdd5376b12e..958c2441942c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -35,7 +35,6 @@ import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.os.Process; -import android.text.Layout; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -65,7 +64,6 @@ import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.time.SystemClock; import java.net.URISyntaxException; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -96,22 +94,22 @@ public class MediaControlPanel { private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS); - // Button IDs for QS controls - static final int[] ACTION_IDS = { - R.id.action0, - R.id.action1, - R.id.action2, - R.id.action3, - R.id.action4 - }; - // Buttons to show in small player when using semantic actions - private static final List<Integer> SEMANTIC_ACTION_IDS = List.of( + private static final List<Integer> SEMANTIC_ACTIONS_COMPACT = List.of( R.id.actionPlayPause, R.id.actionPrev, R.id.actionNext ); + // Buttons to show in small player when using semantic actions + private static final List<Integer> SEMANTIC_ACTIONS_ALL = List.of( + R.id.actionPlayPause, + R.id.actionPrev, + R.id.actionNext, + R.id.action0, + R.id.action1 + ); + private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; protected final Executor mBackgroundExecutor; @@ -127,8 +125,6 @@ public class MediaControlPanel { private MediaController mController; private Lazy<MediaDataManager> mMediaDataManagerLazy; private int mBackgroundColor; - private int mDevicePadding; - private int mAlbumArtSize; // Instance id for logging purpose. protected int mInstanceId = -1; // Uid for the media app. @@ -167,7 +163,6 @@ public class MediaControlPanel { mMediaCarouselController = mediaCarouselController; mFalsingManager = falsingManager; mSystemClock = systemClock; - loadDimens(); mSeekBarViewModel.setLogSmartspaceClick(() -> { logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT, @@ -184,12 +179,6 @@ public class MediaControlPanel { mMediaViewController.onDestroy(); } - private void loadDimens() { - mAlbumArtSize = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_album_size); - mDevicePadding = mContext.getResources() - .getDimensionPixelSize(R.dimen.qs_media_album_device_padding); - } - /** * Get the view holder used to display media controls. * @@ -241,15 +230,14 @@ public class MediaControlPanel { } /** Attaches the player to the player view holder. */ - public void attachPlayer(MediaViewHolder vh, MediaViewController.TYPE playerType) { + public void attachPlayer(MediaViewHolder vh) { mMediaViewHolder = vh; TransitionLayout player = vh.getPlayer(); - boolean useSessionLayout = playerType == MediaViewController.TYPE.PLAYER_SESSION; - mSeekBarObserver = new SeekBarObserver(vh, useSessionLayout); + mSeekBarObserver = new SeekBarObserver(vh); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); - mMediaViewController.attach(player, playerType); + mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER); vh.getPlayer().setOnLongClickListener(v -> { if (!mMediaViewController.isGutsVisible()) { @@ -305,16 +293,6 @@ public class MediaControlPanel { if (mMediaViewHolder == null) { return; } - bindPlayerCommon(data, key); - if (mMediaViewHolder instanceof PlayerViewHolder) { - bindNotificationPlayer(data, key); - } else if (mMediaViewHolder instanceof PlayerSessionViewHolder) { - bindSessionPlayer(data, key); - } - } - - /** Bind elements common to both layouts */ - private void bindPlayerCommon(@NonNull MediaData data, String key) { mKey = key; MediaSession.Token token = data.getToken(); PackageManager packageManager = mContext.getPackageManager(); @@ -371,18 +349,24 @@ public class MediaControlPanel { final MediaController controller = getController(); mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller)); - // Guts label - boolean isDismissible = data.isClearable(); - mMediaViewHolder.getLongPressText().setText(isDismissible - ? R.string.controls_media_close_session - : R.string.controls_media_active_session); + bindOutputSwitcherChip(data); + bindLongPressMenu(data); + bindActionButtons(data); + bindArtworkAndColors(data); + + // TODO: We don't need to refresh this state constantly, only if the state actually changed + // to something which might impact the measurement + mMediaViewController.refreshState(); + } + private void bindOutputSwitcherChip(MediaData data) { // Output switcher chip ViewGroup seamlessView = mMediaViewHolder.getSeamless(); seamlessView.setVisibility(View.VISIBLE); ImageView iconView = mMediaViewHolder.getSeamlessIcon(); TextView deviceName = mMediaViewHolder.getSeamlessText(); final MediaDeviceData device = data.getDevice(); + // Disable clicking on output switcher for invalid devices and resumption controls final boolean seamlessDisabled = (device != null && !device.getEnabled()) || data.getResumption(); @@ -426,9 +410,20 @@ public class MediaControlPanel { mMediaOutputDialogFactory.create(data.getPackageName(), true, mMediaViewHolder.getSeamlessButton()); } - }); + }); + } - // Dismiss + private void bindLongPressMenu(MediaData data) { + boolean isDismissible = data.isClearable(); + String dismissText; + if (isDismissible) { + dismissText = mContext.getString(R.string.controls_media_close_session, data.getApp()); + } else { + dismissText = mContext.getString(R.string.controls_media_active_session); + } + mMediaViewHolder.getLongPressText().setText(dismissText); + + // Dismiss button mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA); mMediaViewHolder.getDismiss().setEnabled(isDismissible); mMediaViewHolder.getDismiss().setOnClickListener(v -> { @@ -443,136 +438,16 @@ public class MediaControlPanel { MediaViewController.GUTS_ANIMATION_DURATION + 100)) { Log.w(TAG, "Manager failed to dismiss media " + mKey); // Remove directly from carousel so user isn't stuck with defunct controls - mMediaCarouselController.removePlayer(key, false, false); + mMediaCarouselController.removePlayer(mKey, false, false); } } else { Log.w(TAG, "Dismiss media with null notification. Token uid=" + data.getToken().getUid()); } }); - - // TODO: We don't need to refresh this state constantly, only if the state actually changed - // to something which might impact the measurement - mMediaViewController.refreshState(); } - /** Bind elements specific to PlayerViewHolder */ - private void bindNotificationPlayer(@NonNull MediaData data, String key) { - ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); - ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - - // Album art - ImageView albumView = mMediaViewHolder.getAlbumView(); - boolean hasArtwork = data.getArtwork() != null; - if (hasArtwork) { - Drawable artwork = getScaledThumbnail(data.getArtwork()); - albumView.setPadding(0, 0, 0, 0); - albumView.setImageDrawable(artwork); - } else { - Drawable deviceIcon; - if (data.getDevice() != null && data.getDevice().getIcon() != null) { - deviceIcon = data.getDevice().getIcon().getConstantState().newDrawable().mutate(); - } else { - deviceIcon = getContext().getDrawable(R.drawable.ic_headphone); - } - deviceIcon.setTintList(ColorStateList.valueOf(mBackgroundColor)); - albumView.setPadding(mDevicePadding, mDevicePadding, mDevicePadding, mDevicePadding); - albumView.setImageDrawable(deviceIcon); - } - - // App icon - use notification icon - ImageView appIconView = mMediaViewHolder.getAppIcon(); - appIconView.clearColorFilter(); - if (data.getAppIcon() != null && !data.getResumption()) { - appIconView.setImageIcon(data.getAppIcon()); - int color = mContext.getColor(R.color.material_dynamic_secondary10); - appIconView.setColorFilter(color); - } else { - // Resume players use launcher icon - appIconView.setColorFilter(getGrayscaleFilter()); - try { - Drawable icon = mContext.getPackageManager().getApplicationIcon( - data.getPackageName()); - appIconView.setImageDrawable(icon); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e); - appIconView.setImageResource(R.drawable.ic_music_note); - } - } - - // Media action buttons - List<MediaAction> actionIcons = data.getActions(); - List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact(); - - // If we got session actions, use those instead - if (data.getSemanticActions() != null) { - MediaButton semanticActions = data.getSemanticActions(); - - actionIcons = new ArrayList<MediaAction>(); - actionIcons.add(semanticActions.getCustom0()); - actionIcons.add(semanticActions.getPrevOrCustom()); - actionIcons.add(semanticActions.getPlayOrPause()); - actionIcons.add(semanticActions.getNextOrCustom()); - actionIcons.add(semanticActions.getCustom1()); - - actionsWhenCollapsed = new ArrayList<Integer>(); - actionsWhenCollapsed.add(1); - actionsWhenCollapsed.add(2); - actionsWhenCollapsed.add(3); - } - - int i = 0; - for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) { - int actionId = ACTION_IDS[i]; - boolean visibleInCompat = actionsWhenCollapsed.contains(i); - final ImageButton button = mMediaViewHolder.getAction(actionId); - MediaAction mediaAction = actionIcons.get(i); - if (mediaAction != null) { - button.setImageIcon(mediaAction.getIcon()); - button.setContentDescription(mediaAction.getContentDescription()); - Runnable action = mediaAction.getAction(); - - if (action == null) { - button.setEnabled(false); - } else { - button.setEnabled(true); - button.setOnClickListener(v -> { - if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT, - /* isRecommendationCard */ false); - action.run(); - } - }); - } - setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); - setVisibleAndAlpha(expandedSet, actionId, true /*visible */); - } else { - button.setImageIcon(null); - button.setContentDescription(null); - button.setEnabled(false); - setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); - // for expanded layout, set as INVISIBLE so that we still reserve space in the UI - expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE); - expandedSet.setAlpha(actionId, 0.0f); - } - } - - // Hide any unused buttons - for (; i < ACTION_IDS.length; i++) { - setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */); - setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /* visible */); - } - // If no actions, set the first view as INVISIBLE so expanded height remains constant - if (actionIcons.size() == 0) { - expandedSet.setVisibility(ACTION_IDS[0], ConstraintSet.INVISIBLE); - } - } - - /** Bind elements specific to PlayerSessionViewHolder */ - private void bindSessionPlayer(@NonNull MediaData data, String key) { - ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); - ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - + private void bindArtworkAndColors(MediaData data) { // Default colors int surfaceColor = mBackgroundColor; int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext, @@ -592,7 +467,7 @@ public class MediaControlPanel { boolean hasArtwork = data.getArtwork() != null; if (hasArtwork) { colorScheme = new ColorScheme(WallpaperColors.fromBitmap(data.getArtwork().getBitmap()), - true); + true); // Scale artwork to fit background int width = mMediaViewHolder.getPlayer().getWidth(); @@ -661,6 +536,14 @@ public class MediaControlPanel { seekbar.setProgressTintList(textColorList); seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary)); + // Action buttons + mMediaViewHolder.getActionPlayPause().setBackgroundTintList(accentColorList); + mMediaViewHolder.getActionPlayPause().setImageTintList( + ColorStateList.valueOf(textPrimaryInverse)); + for (ImageButton button : mMediaViewHolder.getTransparentActionButtons()) { + button.setImageTintList(textColorList); + } + // Output switcher View seamlessView = mMediaViewHolder.getSeamlessButton(); seamlessView.setBackgroundTintList(accentColorList); @@ -669,21 +552,26 @@ public class MediaControlPanel { TextView seamlessText = mMediaViewHolder.getSeamlessText(); seamlessText.setTextColor(surfaceColor); - // Media action buttons + // Long press buttons + mMediaViewHolder.getLongPressText().setTextColor(textColorList); + mMediaViewHolder.getSettings().setImageTintList(accentColorList); + mMediaViewHolder.getCancelText().setTextColor(textColorList); + mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList); + mMediaViewHolder.getDismissText().setTextColor(surfaceColor); + mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList); + } + + private void bindActionButtons(MediaData data) { MediaButton semanticActions = data.getSemanticActions(); - PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder; ImageButton[] genericButtons = new ImageButton[]{ - sessionHolder.getAction0(), - sessionHolder.getAction1(), - sessionHolder.getAction2(), - sessionHolder.getAction3(), - sessionHolder.getAction4()}; - - ImageButton[] semanticButtons = new ImageButton[]{ - sessionHolder.getActionPlayPause(), - sessionHolder.getActionNext(), - sessionHolder.getActionPrev()}; + mMediaViewHolder.getAction0(), + mMediaViewHolder.getAction1(), + mMediaViewHolder.getAction2(), + mMediaViewHolder.getAction3(), + mMediaViewHolder.getAction4()}; + ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); + ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); if (semanticActions != null) { // Hide all the generic buttons for (ImageButton b: genericButtons) { @@ -691,22 +579,15 @@ public class MediaControlPanel { setVisibleAndAlpha(expandedSet, b.getId(), false); } - // Play/pause button has a background - sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList); - setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(), - ColorStateList.valueOf(textPrimaryInverse), collapsedSet, expandedSet, true); - - setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(), - textColorList, collapsedSet, expandedSet, true); - setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(), - textColorList, collapsedSet, expandedSet, true); - setSemanticButton(sessionHolder.getAction0(), semanticActions.getCustom0(), - textColorList, collapsedSet, expandedSet, false); - setSemanticButton(sessionHolder.getAction1(), semanticActions.getCustom1(), - textColorList, collapsedSet, expandedSet, false); + for (int id : SEMANTIC_ACTIONS_ALL) { + boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(id); + ImageButton button = mMediaViewHolder.getAction(id); + MediaAction action = semanticActions.getActionById(id); + setSemanticButton(button, action, collapsedSet, expandedSet, showInCompact); + } } else { - // Hide all the semantic buttons - for (int id : SEMANTIC_ACTION_IDS) { + // Hide buttons that only appear for semantic actions + for (int id : SEMANTIC_ACTIONS_COMPACT) { setVisibleAndAlpha(collapsedSet, id, false); setVisibleAndAlpha(expandedSet, id, false); } @@ -717,13 +598,12 @@ public class MediaControlPanel { int i = 0; for (; i < actions.size(); i++) { boolean showInCompact = actionsWhenCollapsed.contains(i); - setSemanticButton(genericButtons[i], actions.get(i), textColorList, collapsedSet, + setSemanticButton(genericButtons[i], actions.get(i), collapsedSet, expandedSet, showInCompact); } for (; i < 5; i++) { // Hide any unused buttons - setSemanticButton(genericButtons[i], null, textColorList, collapsedSet, - expandedSet, false); + setSemanticButton(genericButtons[i], null, collapsedSet, expandedSet, false); } } @@ -732,21 +612,10 @@ public class MediaControlPanel { expandedSet.setVisibility(R.id.media_progress_bar, seekbarEnabled ? ConstraintSet.VISIBLE : ConstraintSet.INVISIBLE); expandedSet.setAlpha(R.id.media_progress_bar, seekbarEnabled ? 1.0f : 0.0f); - - // Long press buttons - mMediaViewHolder.getLongPressText().setTextColor(textColorList); - mMediaViewHolder.getSettingsText().setTextColor(textColorList); - mMediaViewHolder.getSettingsText().setBackgroundTintList(accentColorList); - mMediaViewHolder.getCancelText().setTextColor(textColorList); - mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList); - mMediaViewHolder.getDismissText().setTextColor(textColorList); - mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList); } private void setSemanticButton(final ImageButton button, MediaAction mediaAction, - ColorStateList fgColor, ConstraintSet collapsedSet, ConstraintSet expandedSet, - boolean showInCompact) { - button.setImageTintList(fgColor); + ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { if (mediaAction != null) { button.setImageIcon(mediaAction.getIcon()); button.setContentDescription(mediaAction.getContentDescription()); @@ -982,60 +851,15 @@ public class MediaControlPanel { } private void openGuts() { - ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); - ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - - boolean wasTruncated = false; - Layout l = null; if (mMediaViewHolder != null) { mMediaViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION); - l = mMediaViewHolder.getSettingsText().getLayout(); } else if (mRecommendationViewHolder != null) { mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION); - l = mRecommendationViewHolder.getSettingsText().getLayout(); - } - if (l != null) { - wasTruncated = l.getEllipsisCount(0) > 0; } - mMediaViewController.setShouldHideGutsSettings(wasTruncated); - if (wasTruncated) { - // not enough room for the settings button to show fully, let's hide it - expandedSet.constrainMaxWidth(R.id.settings, 0); - collapsedSet.constrainMaxWidth(R.id.settings, 0); - } - mMediaViewController.openGuts(); } /** - * Scale drawable to fit into the square album art thumbnail - */ - @UiThread - private Drawable getScaledThumbnail(Icon icon) { - if (icon == null) { - return null; - } - // Let's scale down the View, such that the content always nicely fills the view. - // ThumbnailUtils actually scales it down such that it may not be filled for odd aspect - // ratios - Drawable drawable = icon.loadDrawable(mContext); - float aspectRatio = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth(); - Rect bounds; - if (aspectRatio > 1.0f) { - bounds = new Rect(0, 0, mAlbumArtSize, (int) (mAlbumArtSize * aspectRatio)); - } else { - bounds = new Rect(0, 0, (int) (mAlbumArtSize / aspectRatio), mAlbumArtSize); - } - if (bounds.width() > mAlbumArtSize || bounds.height() > mAlbumArtSize) { - float offsetX = (bounds.width() - mAlbumArtSize) / 2.0f; - float offsetY = (bounds.height() - mAlbumArtSize) / 2.0f; - bounds.offset((int) -offsetX, (int) -offsetY); - } - drawable.setBounds(bounds); - return drawable; - } - - /** * Scale artwork to fill the background of the panel */ @UiThread diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 4cf6291fe35b..f1712dbc3f1b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -20,6 +20,7 @@ import android.app.PendingIntent import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.media.session.MediaSession +import com.android.systemui.R /** State of a media view. */ data class MediaData( @@ -154,7 +155,18 @@ data class MediaButton( * Second custom action space */ var custom1: MediaAction? = null -) +) { + fun getActionById(id: Int): MediaAction? { + return when (id) { + R.id.actionPlayPause -> playOrPause + R.id.actionNext -> nextOrCustom + R.id.actionPrev -> prevOrCustom + R.id.action0 -> custom0 + R.id.action1 -> custom1 + else -> null + } + } +} /** State of a media action. */ data class MediaAction( diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt index 59237d936d72..b85ae4820d49 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt @@ -35,13 +35,6 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { } /** - * Check whether media controls should use the new session-based layout - */ - fun useMediaSessionLayout(): Boolean { - return featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT) - } - - /** * Check whether we support displaying information about mute await connections. */ fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index d472aeee1073..d978d02d63f1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -98,6 +98,10 @@ class MediaHierarchyManager @Inject constructor( private var currentBounds = Rect() private var animationStartBounds: Rect = Rect() + private var animationStartClipping = Rect() + private var currentClipping = Rect() + private var targetClipping = Rect() + /** * The cross fade progress at the start of the animation. 0.5f means it's just switching between * the start and the end location and the content is fully faded, while 0.75f means that we're @@ -144,7 +148,8 @@ class MediaHierarchyManager @Inject constructor( } interpolateBounds(animationStartBounds, targetBounds, boundsProgress, result = currentBounds) - applyState(currentBounds, currentAlpha) + resolveClipping(currentClipping) + applyState(currentBounds, currentAlpha, clipBounds = currentClipping) } addListener(object : AnimatorListenerAdapter() { private var cancelled: Boolean = false @@ -169,6 +174,12 @@ class MediaHierarchyManager @Inject constructor( }) } + private fun resolveClipping(result: Rect) { + if (animationStartClipping.isEmpty) result.set(targetClipping) + else if (targetClipping.isEmpty) result.set(animationStartClipping) + else result.setIntersect(animationStartClipping, targetClipping) + } + private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1) /** * The last location where this view was at before going to the desired location. This is @@ -629,10 +640,12 @@ class MediaHierarchyManager @Inject constructor( // We also go in here in case the view was detached, since the bounds wouldn't // be correct anymore animationStartBounds.set(currentBounds) + animationStartClipping.set(currentClipping) } else { // otherwise, let's take the freshest state, since the current one could // be outdated animationStartBounds.set(previousHost.currentBounds) + animationStartClipping.set(previousHost.currentClipping) } val transformationType = calculateTransformationType() var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE @@ -745,7 +758,7 @@ class MediaHierarchyManager @Inject constructor( // Let's immediately apply the target state (which is interpolated) if there is // no animation running. Otherwise the animation update will already update // the location - applyState(targetBounds, carouselAlpha) + applyState(targetBounds, carouselAlpha, clipBounds = targetClipping) } } @@ -769,9 +782,11 @@ class MediaHierarchyManager @Inject constructor( val newBounds = endHost.currentBounds val previousBounds = starthost.currentBounds targetBounds = interpolateBounds(previousBounds, newBounds, progress) + targetClipping = endHost.currentClipping } else if (endHost != null) { val bounds = endHost.currentBounds targetBounds.set(bounds) + targetClipping = endHost.currentClipping } } @@ -879,8 +894,14 @@ class MediaHierarchyManager @Inject constructor( /** * Apply the current state to the view, updating it's bounds and desired state */ - private fun applyState(bounds: Rect, alpha: Float, immediately: Boolean = false) { + private fun applyState( + bounds: Rect, + alpha: Float, + immediately: Boolean = false, + clipBounds: Rect = EMPTY_RECT + ) { currentBounds.set(bounds) + currentClipping = clipBounds carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading() val startLocation = if (onlyUseEndState) -1 else previousLocation @@ -889,6 +910,10 @@ class MediaHierarchyManager @Inject constructor( mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately) updateHostAttachment() if (currentAttachmentLocation == IN_OVERLAY) { + // Setting the clipping on the hierarchy of `mediaFrame` does not work + if (!currentClipping.isEmpty) { + currentBounds.intersect(currentClipping) + } mediaFrame.setLeftTopRightBottom( currentBounds.left, currentBounds.top, @@ -1113,6 +1138,7 @@ class MediaHierarchyManager @Inject constructor( const val TRANSFORMATION_TYPE_FADE = 1 } } +private val EMPTY_RECT = Rect() @IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [ MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index eb209f723cb5..d08b6f825f41 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -55,6 +55,13 @@ class MediaHost constructor( return field } + /** + * Set the clipping that this host should use, based on its parent's bounds. + * + * Use [Rect.set]. + */ + val currentClipping = Rect() + private val listener = object : MediaDataManager.Listener { override fun onMediaDataLoaded( key: String, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index a60016b23a7c..5210499ca311 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -22,7 +22,10 @@ import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.R import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.util.animation.* +import com.android.systemui.util.animation.MeasurementOutput +import com.android.systemui.util.animation.TransitionLayout +import com.android.systemui.util.animation.TransitionLayoutController +import com.android.systemui.util.animation.TransitionViewState import javax.inject.Inject /** @@ -40,7 +43,7 @@ class MediaViewController @Inject constructor( * session-based player, or recommendation */ enum class TYPE { - PLAYER, PLAYER_SESSION, RECOMMENDATION + PLAYER, RECOMMENDATION } companion object { @@ -186,11 +189,6 @@ class MediaViewController @Inject constructor( var isGutsVisible = false private set - /** - * Whether the settings button in the guts should be visible - */ - var shouldHideGutsSettings = false - init { mediaHostStatesManager.addController(this) layoutController.sizeChangedListener = { width: Int, height: Int -> @@ -259,13 +257,11 @@ class MediaViewController @Inject constructor( */ private fun setGutsViewState(viewState: TransitionViewState) { val controlsIds = when (type) { - TYPE.PLAYER -> PlayerViewHolder.controlsIds - TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.controlsIds + TYPE.PLAYER -> MediaViewHolder.controlsIds TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds } val gutsIds = when (type) { - TYPE.PLAYER -> PlayerViewHolder.gutsIds - TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.gutsIds + TYPE.PLAYER -> MediaViewHolder.gutsIds TYPE.RECOMMENDATION -> RecommendationViewHolder.gutsIds } controlsIds.forEach { id -> @@ -279,23 +275,22 @@ class MediaViewController @Inject constructor( viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f viewState.widgetStates.get(id)?.gone = !isGutsVisible } - if (shouldHideGutsSettings) { - viewState.widgetStates.get(R.id.settings)?.gone = true - } } /** * Apply squishFraction to a copy of viewState such that the cached version is untouched. */ - private fun squishViewState(viewState: TransitionViewState, - squishFraction: Float): TransitionViewState { + private fun squishViewState( + viewState: TransitionViewState, + squishFraction: Float + ): TransitionViewState { val squishedViewState = viewState.copy() squishedViewState.height = (squishedViewState.height * squishFraction).toInt() val albumArtViewState = viewState.widgetStates.get(R.id.album_art) if (albumArtViewState != null) { albumArtViewState.height = squishedViewState.height } - return squishedViewState; + return squishedViewState } /** @@ -314,7 +309,7 @@ class MediaViewController @Inject constructor( if (viewState != null) { // we already have cached this measurement, let's continue if (state.squishFraction < 1f) { - return squishViewState(viewState, state.squishFraction); + return squishViewState(viewState, state.squishFraction) } return viewState } @@ -351,7 +346,7 @@ class MediaViewController @Inject constructor( state.expansion) } if (state.squishFraction < 1f) { - return squishViewState(result, state.squishFraction); + return squishViewState(result, state.squishFraction) } return result } @@ -492,10 +487,6 @@ class MediaViewController @Inject constructor( // These XML resources contain ConstraintSets that will apply to this player type's layout when (type) { TYPE.PLAYER -> { - collapsedLayout.load(context, R.xml.media_collapsed) - expandedLayout.load(context, R.xml.media_expanded) - } - TYPE.PLAYER_SESSION -> { collapsedLayout.load(context, R.xml.media_session_collapsed) expandedLayout.load(context, R.xml.media_session_expanded) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt index 5f606969153c..836b5cb3e2b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt @@ -17,6 +17,7 @@ package com.android.systemui.media import android.util.Log +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageButton @@ -29,9 +30,9 @@ import com.android.systemui.util.animation.TransitionLayout private const val TAG = "MediaViewHolder" /** - * Parent class for different media player views + * Holder class for media player view */ -abstract class MediaViewHolder constructor(itemView: View) { +class MediaViewHolder constructor(itemView: View) { val player = itemView as TransitionLayout // Player information @@ -57,10 +58,12 @@ abstract class MediaViewHolder constructor(itemView: View) { val cancelText = itemView.requireViewById<TextView>(R.id.cancel_text) val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss) val dismissText = itemView.requireViewById<TextView>(R.id.dismiss_text) - val settings = itemView.requireViewById<View>(R.id.settings) - val settingsText = itemView.requireViewById<TextView>(R.id.settings_text) + val settings = itemView.requireViewById<ImageButton>(R.id.settings) // Action Buttons + val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause) + val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext) + val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev) val action0 = itemView.requireViewById<ImageButton>(R.id.action0) val action1 = itemView.requireViewById<ImageButton>(R.id.action1) val action2 = itemView.requireViewById<ImageButton>(R.id.action2) @@ -73,6 +76,9 @@ abstract class MediaViewHolder constructor(itemView: View) { it.registerLightSource(cancel) it.registerLightSource(dismiss) it.registerLightSource(settings) + it.registerLightSource(actionPlayPause) + it.registerLightSource(actionNext) + it.registerLightSource(actionPrev) it.registerLightSource(action0) it.registerLightSource(action1) it.registerLightSource(action2) @@ -81,7 +87,33 @@ abstract class MediaViewHolder constructor(itemView: View) { } } - abstract fun getAction(id: Int): ImageButton + fun getAction(id: Int): ImageButton { + return when (id) { + R.id.actionPlayPause -> actionPlayPause + R.id.actionNext -> actionNext + R.id.actionPrev -> actionPrev + R.id.action0 -> action0 + R.id.action1 -> action1 + R.id.action2 -> action2 + R.id.action3 -> action3 + R.id.action4 -> action4 + else -> { + throw IllegalArgumentException() + } + } + } + + fun getTransparentActionButtons(): List<ImageButton> { + return listOf( + actionNext, + actionPrev, + action0, + action1, + action2, + action3, + action4 + ) + } fun marquee(start: Boolean, delay: Long) { val longPressTextHandler = longPressText.getHandler() @@ -91,4 +123,52 @@ abstract class MediaViewHolder constructor(itemView: View) { } longPressTextHandler.postDelayed({ longPressText.setSelected(start) }, delay) } + + companion object { + /** + * Creates a MediaViewHolder. + * + * @param inflater LayoutInflater to use to inflate the layout. + * @param parent Parent of inflated view. + */ + @JvmStatic fun create( + inflater: LayoutInflater, + parent: ViewGroup + ): MediaViewHolder { + val mediaView = inflater.inflate(R.layout.media_session_view, parent, false) + mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null) + // Because this media view (a TransitionLayout) is used to measure and layout the views + // in various states before being attached to its parent, we can't depend on the default + // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. + mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE + return MediaViewHolder(mediaView).apply { + // Media playback is in the direction of tape, not time, so it stays LTR + seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR + } + } + + val controlsIds = setOf( + R.id.icon, + R.id.app_name, + R.id.header_title, + R.id.header_artist, + R.id.media_seamless, + R.id.media_progress_bar, + R.id.actionPlayPause, + R.id.actionNext, + R.id.actionPrev, + R.id.action0, + R.id.action1, + R.id.action2, + R.id.action3, + R.id.action4, + R.id.icon + ) + val gutsIds = setOf( + R.id.remove_text, + R.id.cancel, + R.id.dismiss, + R.id.settings + ) + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt deleted file mode 100644 index 6928ebb8bb32..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageButton -import com.android.systemui.R - -/** - * ViewHolder for a media player with MediaSession-based controls - */ -class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) { - - // Action Buttons - val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause) - val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext) - val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev) - - init { - (player.background as IlluminationDrawable).let { - it.registerLightSource(actionPlayPause) - it.registerLightSource(actionNext) - it.registerLightSource(actionPrev) - } - } - - override fun getAction(id: Int): ImageButton { - return when (id) { - R.id.actionPlayPause -> actionPlayPause - R.id.actionNext -> actionNext - R.id.actionPrev -> actionPrev - R.id.action0 -> action0 - R.id.action1 -> action1 - R.id.action2 -> action2 - R.id.action3 -> action3 - R.id.action4 -> action4 - else -> { - throw IllegalArgumentException() - } - } - } - - companion object { - /** - * Creates a PlayerSessionViewHolder. - * - * @param inflater LayoutInflater to use to inflate the layout. - * @param parent Parent of inflated view. - */ - @JvmStatic fun create( - inflater: LayoutInflater, - parent: ViewGroup - ): PlayerSessionViewHolder { - val mediaView = inflater.inflate(R.layout.media_session_view, parent, false) - mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null) - // Because this media view (a TransitionLayout) is used to measure and layout the views - // in various states before being attached to its parent, we can't depend on the default - // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. - mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE - return PlayerSessionViewHolder(mediaView).apply { - // Media playback is in the direction of tape, not time, so it stays LTR - seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR - } - } - - val controlsIds = setOf( - R.id.icon, - R.id.app_name, - R.id.header_title, - R.id.header_artist, - R.id.media_seamless, - R.id.media_progress_bar, - R.id.actionPlayPause, - R.id.actionNext, - R.id.actionPrev, - R.id.action0, - R.id.action1, - R.id.action2, - R.id.action3, - R.id.action4, - R.id.icon - ) - val gutsIds = setOf( - R.id.remove_text, - R.id.cancel, - R.id.dismiss, - R.id.settings - ) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt deleted file mode 100644 index dd3fa89dea66..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageButton -import android.widget.TextView -import com.android.systemui.R - -/** - * ViewHolder for a media player. - */ -class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) { - - // Seek bar - val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time) - override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time) - override val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time) - - override fun getAction(id: Int): ImageButton { - return when (id) { - R.id.action0 -> action0 - R.id.action1 -> action1 - R.id.action2 -> action2 - R.id.action3 -> action3 - R.id.action4 -> action4 - else -> { - throw IllegalArgumentException() - } - } - } - - companion object { - /** - * Creates a PlayerViewHolder. - * - * @param inflater LayoutInflater to use to inflate the layout. - * @param parent Parent of inflated view. - */ - @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder { - val mediaView = inflater.inflate(R.layout.media_view, parent, false) - mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null) - // Because this media view (a TransitionLayout) is used to measure and layout the views - // in various states before being attached to its parent, we can't depend on the default - // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. - mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE - return PlayerViewHolder(mediaView).apply { - // Media playback is in the direction of tape, not time, so it stays LTR - seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR - progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR - } - } - - val controlsIds = setOf( - R.id.icon, - R.id.app_name, - R.id.album_art, - R.id.header_title, - R.id.header_artist, - R.id.media_seamless, - R.id.notification_media_progress_time, - R.id.media_progress_bar, - R.id.action0, - R.id.action1, - R.id.action2, - R.id.action3, - R.id.action4, - R.id.icon - ) - val gutsIds = setOf( - R.id.remove_text, - R.id.cancel, - R.id.dismiss, - R.id.settings - ) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index 57701ab618c9..e5b41b1a6771 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -27,28 +27,17 @@ import com.android.systemui.R * <p>Updates the seek bar views in response to changes to the model. */ class SeekBarObserver( - private val holder: MediaViewHolder, - private val useSessionLayout: Boolean + private val holder: MediaViewHolder ) : Observer<SeekBarViewModel.Progress> { val seekBarEnabledMaxHeight = holder.seekBar.context.resources .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height) val seekBarDisabledHeight = holder.seekBar.context.resources .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height) - val seekBarEnabledVerticalPadding = if (useSessionLayout) { - holder.seekBar.context.resources + val seekBarEnabledVerticalPadding = holder.seekBar.context.resources .getDimensionPixelSize(R.dimen.qs_media_session_enabled_seekbar_vertical_padding) - } else { - holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_vertical_padding) - } - val seekBarDisabledVerticalPadding = if (useSessionLayout) { - holder.seekBar.context.resources + val seekBarDisabledVerticalPadding = holder.seekBar.context.resources .getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding) - } else { - holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding) - } init { val seekBarProgressWavelength = holder.seekBar.context.resources diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index a6464829623f..f4fcf10e7bb4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -169,12 +169,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device), true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, false /* showStatus */); - mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(true); - mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { - onCheckBoxClicked(false, device); - }); + mSeekBar.setOnClickListener(null); + mSeekBar.setOnClickListener(v -> onGroupActionTriggered(false, device)); setCheckBoxColor(mCheckBox, mController.getColorItemContent()); initSeekbar(device); } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) { @@ -188,17 +186,13 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { initSeekbar(device); mCurrentActivePosition = position; } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { - mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(false); - mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { - onCheckBoxClicked(true, device); - }); + mContainerLayout.setOnClickListener(v -> onGroupActionTriggered(true, device)); setCheckBoxColor(mCheckBox, mController.getColorItemContent()); setSingleLineLayout(getItemTitle(device), false /* bFocused */, false /* showSeekBar */, false /* showProgressBar */, false /* showStatus */); - mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); } else { setSingleLineLayout(getItemTitle(device), false /* bFocused */); mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); @@ -228,7 +222,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } } - private void onCheckBoxClicked(boolean isChecked, MediaDevice device) { + private void onGroupActionTriggered(boolean isChecked, MediaDevice device) { if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { mController.addDeviceToPlayMedia(device); } else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(), diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index ea7f7f2bb646..1cc96c0fd0de 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -637,7 +637,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } private boolean isPlayBackInfoLocal() { - return mMediaController.getPlaybackInfo() != null + return mMediaController != null + && mMediaController.getPlaybackInfo() != null && mMediaController.getPlaybackInfo().getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL; } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java new file mode 100644 index 000000000000..0a3c5bf24b8b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dialog; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.SeekBar; + +/** + * Customized seekbar used by MediaOutputDialog, which only changes progress when dragging, + * otherwise performs click. + */ +public class MediaOutputSeekbar extends SeekBar { + private static final int DRAGGING_THRESHOLD = 20; + private boolean mIsDragging = false; + + public MediaOutputSeekbar(Context context) { + super(context); + } + + public MediaOutputSeekbar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int width = getWidth() + - getPaddingLeft() + - getPaddingRight(); + int thumbPos = getPaddingLeft() + + width + * getProgress() + / getMax(); + if (event.getAction() == MotionEvent.ACTION_DOWN + && Math.abs(event.getX() - thumbPos) < DRAGGING_THRESHOLD) { + mIsDragging = true; + super.onTouchEvent(event); + } else if (event.getAction() == MotionEvent.ACTION_MOVE && mIsDragging) { + super.onTouchEvent(event); + } else if (event.getAction() == MotionEvent.ACTION_UP && mIsDragging) { + mIsDragging = false; + super.onTouchEvent(event); + } else if (event.getAction() == MotionEvent.ACTION_UP && !mIsDragging) { + performClick(); + } + return true; + } + + @Override + public boolean performClick() { + return super.performClick(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index d8d86779ea0a..fab19d62b70d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -86,7 +86,6 @@ import android.view.HapticFeedbackConstants; import android.view.InsetsState.InternalInsetsType; import android.view.InsetsVisibilities; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.View; @@ -98,6 +97,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; @@ -111,8 +111,10 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; +import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener; import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.buttons.DeadZone; @@ -140,7 +142,10 @@ import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.ViewController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; @@ -149,15 +154,15 @@ import java.util.Locale; import java.util.Optional; import java.util.function.Consumer; +import javax.inject.Inject; + import dagger.Lazy; -import dagger.assisted.Assisted; -import dagger.assisted.AssistedFactory; -import dagger.assisted.AssistedInject; /** * Contains logic for a navigation bar view. */ -public class NavigationBar implements View.OnAttachStateChangeListener, Callbacks { +@NavigationBarScope +public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks { public static final String TAG = "NavigationBar"; private static final boolean DEBUG = false; @@ -172,12 +177,14 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback private static final long AUTODIM_TIMEOUT_MS = 2250; private final Context mContext; + private final Bundle mSavedState; private final WindowManager mWindowManager; private final AccessibilityManager mAccessibilityManager; private final DeviceProvisionedController mDeviceProvisionedController; private final StatusBarStateController mStatusBarStateController; private final MetricsLogger mMetricsLogger; private final Lazy<AssistManager> mAssistManagerLazy; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final SysUiState mSysUiFlagsContainer; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; private final ShadeController mShadeController; @@ -188,15 +195,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback private final CommandQueue mCommandQueue; private final Optional<Pip> mPipOptional; private final Optional<Recents> mRecentsOptional; + private final DeviceConfigProxy mDeviceConfigProxy; private final Optional<BackAnimation> mBackAnimation; private final Handler mHandler; private final NavigationBarOverlayController mNavbarOverlayController; private final UiEventLogger mUiEventLogger; private final NavBarHelper mNavBarHelper; private final NotificationShadeDepthController mNotificationShadeDepthController; - - private Bundle mSavedState; - private NavigationBarView mNavigationBarView; private NavigationBarFrame mFrame; private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING; @@ -256,7 +261,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback private int mCurrentRotation; private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener; private boolean mShowOrientedHandleForImmersiveMode; - private DeadZone mDeadZone; + private final DeadZone mDeadZone; private boolean mImeVisible; @com.android.internal.annotations.VisibleForTesting @@ -311,7 +316,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback // TODO(b/198002034): Content observers currently can still be called back after // being unregistered, and in this case we can ignore the change if the nav bar // has been destroyed already - if (mNavigationBarView == null) { + if (mView == null) { return; } mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); @@ -322,14 +327,14 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { @Override public void onConnectionChanged(boolean isConnected) { - mNavigationBarView.updateStates(); + mView.updateStates(); updateScreenPinningGestures(); } @Override public void onQuickStepStarted() { // Use navbar dragging as a signal to hide the rotate button - mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false); + mView.getRotationButtonController().setRotateSuggestionButtonState(false); // Hide the notifications panel when quick step starts mShadeController.collapsePanel(true /* animate */); @@ -366,12 +371,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback // Disallow home handle animations when in gestural animate = false; forceVisible = mAllowForceNavBarHandleOpaque && mForceNavBarHandleOpaque; - buttonDispatcher = mNavigationBarView.getHomeHandle(); + buttonDispatcher = mView.getHomeHandle(); if (getBarTransitions() != null) { getBarTransitions().setBackgroundOverrideAlpha(alpha); } } else if (QuickStepContract.isSwipeUpMode(mNavBarMode)) { - buttonDispatcher = mNavigationBarView.getBackButton(); + buttonDispatcher = mView.getBackButton(); } if (buttonDispatcher != null) { buttonDispatcher.setVisibility( @@ -382,7 +387,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback @Override public void onHomeRotationEnabled(boolean enabled) { - mNavigationBarView.getRotationButtonController().setHomeRotationEnabled(enabled); + mView.getRotationButtonController().setHomeRotationEnabled(enabled); } @Override @@ -390,20 +395,18 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback // If the overview has fixed orientation that may change display to natural rotation, // we don't want the user rotation to be reset. So after user returns to application, // it can keep in the original rotation. - mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce(); + mView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce(); } @Override public void onTaskbarStatusUpdated(boolean visible, boolean stashed) { - mNavigationBarView - .getFloatingRotationButton() - .onTaskbarStateChanged(visible, stashed); + mView.getFloatingRotationButton().onTaskbarStateChanged(visible, stashed); } @Override public void onToggleRecentApps() { // The same case as onOverviewShown but only for 3-button navigation. - mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce(); + mView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce(); } }; @@ -416,11 +419,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback }; private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true); - private final Runnable mEnableLayoutTransitions = () -> - mNavigationBarView.setLayoutTransitionsEnabled(true); + private final Runnable mEnableLayoutTransitions = () -> mView.setLayoutTransitionsEnabled(true); private final Runnable mOnVariableDurationHomeLongClick = () -> { - if (onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView())) { - mNavigationBarView.getHomeButton().getCurrentView().performHapticFeedback( + if (onHomeLongClick(mView.getHomeButton().getCurrentView())) { + mView.getHomeButton().getCurrentView().performHapticFeedback( HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); } @@ -439,7 +441,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mHomeButtonLongPressDurationMs = Optional.of( properties.getLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 0) ).filter(duration -> duration != 0); - if (mNavigationBarView != null) { + if (mView != null) { reconfigureHomeLongClick(); } } @@ -469,14 +471,17 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback return; } mHasBlurs = hasBlurs; - mNavigationBarView.setWindowHasBlurs(hasBlurs); + mView.setWindowHasBlurs(hasBlurs); } }; - @AssistedInject + @Inject NavigationBar( - @Assisted Context context, - @Assisted WindowManager windowManager, + NavigationBarView navigationBarView, + NavigationBarFrame navigationBarFrame, + @Nullable Bundle savedState, + @DisplayId Context context, + @DisplayId WindowManager windowManager, Lazy<AssistManager> assistManagerLazy, AccessibilityManager accessibilityManager, DeviceProvisionedController deviceProvisionedController, @@ -484,6 +489,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, StatusBarStateController statusBarStateController, + StatusBarKeyguardViewManager statusBarKeyguardViewManager, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, @@ -503,14 +509,20 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback AutoHideController.Factory autoHideControllerFactory, Optional<TelecomManager> telecomManagerOptional, InputMethodManager inputMethodManager, + DeadZone deadZone, + DeviceConfigProxy deviceConfigProxy, Optional<BackAnimation> backAnimation) { + super(navigationBarView); + mFrame = navigationBarFrame; mContext = context; + mSavedState = savedState; mWindowManager = windowManager; mAccessibilityManager = accessibilityManager; mDeviceProvisionedController = deviceProvisionedController; mStatusBarStateController = statusBarStateController; mMetricsLogger = metricsLogger; mAssistManagerLazy = assistManagerLazy; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mSysUiFlagsContainer = sysUiFlagsContainer; mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mShadeController = shadeController; @@ -521,6 +533,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mCommandQueue = commandQueue; mPipOptional = pipOptional; mRecentsOptional = recentsOptional; + mDeadZone = deadZone; + mDeviceConfigProxy = deviceConfigProxy; mBackAnimation = backAnimation; mHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; @@ -538,25 +552,23 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } public NavigationBarView getView() { - return mNavigationBarView; + return mView; } - public View createView(Bundle savedState, boolean initialVisibility) { - mFrame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate( - R.layout.navigation_bar_window, null); - View barView = LayoutInflater.from(mFrame.getContext()).inflate( - R.layout.navigation_bar, mFrame); - barView.addOnAttachStateChangeListener(this); - mNavigationBarView = barView.findViewById(R.id.navigation_bar_view); - mDeadZone = new DeadZone(mNavigationBarView); - mNavigationBarView.setTouchHandler(mTouchHandler); - mNavigationBarView.setNavBarMode(mNavBarMode); + @Override + public void onInit() { + // TODO: A great deal of this code should probalby live in onViewAttached. + // It should also has corresponding cleanup in onViewDetached. + mView.setTouchHandler(mTouchHandler); + mView.setNavBarMode(mNavBarMode); - mNavigationBarView.updateRotationButton(); + mView.updateRotationButton(); - mNavigationBarView.setVisibility(initialVisibility ? View.VISIBLE : View.INVISIBLE); + mView.setVisibility( + mStatusBarKeyguardViewManager.isNavBarVisible() ? View.VISIBLE : View.INVISIBLE); + + if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView); - if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView); mWindowManager.addView(mFrame, getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration .getRotation())); @@ -568,26 +580,25 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mNavBarHelper.init(); mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean( R.bool.allow_force_nav_bar_handle_opaque); - mForceNavBarHandleOpaque = DeviceConfig.getBoolean( + mForceNavBarHandleOpaque = mDeviceConfigProxy.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true); - mHomeButtonLongPressDurationMs = Optional.of(DeviceConfig.getLong( + mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong( DeviceConfig.NAMESPACE_SYSTEMUI, HOME_BUTTON_LONG_PRESS_DURATION_MS, /* defaultValue = */ 0 )).filter(duration -> duration != 0); - DeviceConfig.addOnPropertiesChangedListener( + mDeviceConfigProxy.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener); - if (savedState != null) { - mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0); - mDisabledFlags2 = savedState.getInt(EXTRA_DISABLE2_STATE, 0); - mAppearance = savedState.getInt(EXTRA_APPEARANCE, 0); - mBehavior = savedState.getInt(EXTRA_BEHAVIOR, 0); - mTransientShown = savedState.getBoolean(EXTRA_TRANSIENT_STATE, false); + if (mSavedState != null) { + mDisabledFlags1 = mSavedState.getInt(EXTRA_DISABLE_STATE, 0); + mDisabledFlags2 = mSavedState.getInt(EXTRA_DISABLE2_STATE, 0); + mAppearance = mSavedState.getInt(EXTRA_APPEARANCE, 0); + mBehavior = mSavedState.getInt(EXTRA_BEHAVIOR, 0); + mTransientShown = mSavedState.getBoolean(EXTRA_TRANSIENT_STATE, false); } - mSavedState = savedState; // Respect the latest disabled-flags. mCommandQueue.recomputeDisableFlags(mDisplayId, false); @@ -595,14 +606,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup(); mDeviceProvisionedController.addCallback(mUserSetupListener); mNotificationShadeDepthController.addListener(mDepthListener); - - return barView; } public void destroyView() { setAutoHideController(/* autoHideController */ null); mCommandQueue.removeCallback(this); - mWindowManager.removeViewImmediate(mNavigationBarView.getRootView()); + mWindowManager.removeViewImmediate(mView.getRootView()); mNavigationModeController.removeListener(mModeChangedListener); mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); @@ -610,29 +619,29 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mDeviceProvisionedController.removeCallback(mUserSetupListener); mNotificationShadeDepthController.removeListener(mDepthListener); - DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); + mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); } @Override - public void onViewAttachedToWindow(View v) { - final Display display = v.getDisplay(); - mNavigationBarView.setComponents(mRecentsOptional); - mNavigationBarView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController()); - mNavigationBarView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer); - mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); - mNavigationBarView.setOnTouchListener(this::onNavigationTouch); + public void onViewAttached() { + final Display display = mView.getDisplay(); + mView.setComponents(mRecentsOptional); + mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController()); + mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer); + mView.setOnVerticalChangedListener(this::onVerticalChanged); + mView.setOnTouchListener(this::onNavigationTouch); if (mSavedState != null) { - mNavigationBarView.getLightTransitionsController().restoreState(mSavedState); + mView.getLightTransitionsController().restoreState(mSavedState); } setNavigationIconHints(mNavigationIconHints); - mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); - mNavigationBarView.setBehavior(mBehavior); - mNavigationBarView.setNavBarMode(mNavBarMode); + mView.setWindowVisible(isNavBarWindowVisible()); + mView.setBehavior(mBehavior); + mView.setNavBarMode(mNavBarMode); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); - mPipOptional.ifPresent(mNavigationBarView::addPipExclusionBoundsChangeListener); - mBackAnimation.ifPresent(mNavigationBarView::registerBackAnimation); + mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener); + mBackAnimation.ifPresent(mView::registerBackAnimation); prepareNavigationBarView(); checkNavBarModes(); @@ -650,7 +659,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback // Currently there is no accelerometer sensor on non-default display. if (mIsOnDefaultDisplay) { final RotationButtonController rotationButtonController = - mNavigationBarView.getRotationButtonController(); + mView.getRotationButtonController(); rotationButtonController.setRotationCallback(mRotationWatcher); // Reset user rotation pref to match that of the WindowManager if starting in locked @@ -684,12 +693,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } @Override - public void onViewDetachedFromWindow(View v) { + public void onViewDetached() { final RotationButtonController rotationButtonController = - mNavigationBarView.getRotationButtonController(); + mView.getRotationButtonController(); rotationButtonController.setRotationCallback(null); - mNavigationBarView.getBarTransitions().destroy(); - mNavigationBarView.getLightTransitionsController().destroy(mContext); + mView.getBarTransitions().destroy(); + mView.getLightTransitionsController().destroy(mContext); mOverviewProxyService.removeCallback(mOverviewProxyListener); mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); if (mOrientationHandle != null) { @@ -703,9 +712,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mHandler.removeCallbacks(mOnVariableDurationHomeLongClick); mHandler.removeCallbacks(mEnableLayoutTransitions); mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); - mPipOptional.ifPresent(mNavigationBarView::removePipExclusionBoundsChangeListener); + mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener); mFrame = null; - mNavigationBarView = null; mOrientationHandle = null; } @@ -716,7 +724,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback outState.putInt(EXTRA_APPEARANCE, mAppearance); outState.putInt(EXTRA_BEHAVIOR, mBehavior); outState.putBoolean(EXTRA_TRANSIENT_STATE, mTransientShown); - mNavigationBarView.getLightTransitionsController().saveState(outState); + mView.getLightTransitionsController().saveState(outState); } /** @@ -779,7 +787,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mOrientationHandle.mapRectFromViewToScreenCoords(boundsOnScreen, true); Rect boundsRounded = new Rect(); boundsOnScreen.roundOut(boundsRounded); - mNavigationBarView.setOrientedHandleSamplingRegion(boundsRounded); + mView.setOrientedHandleSamplingRegion(boundsRounded); }; mOrientationHandle.getViewTreeObserver().addOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); @@ -808,7 +816,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback case Surface.ROTATION_90: case Surface.ROTATION_270: height = dispSize.height(); - width = mNavigationBarView.getHeight(); + width = mView.getHeight(); break; case Surface.ROTATION_180: case Surface.ROTATION_0: @@ -818,7 +826,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback return; } width = dispSize.width(); - height = mNavigationBarView.getHeight(); + height = mView.getHeight(); break; } @@ -828,7 +836,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mOrientationParams.height = height; mOrientationParams.width = width; mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams); - mNavigationBarView.setVisibility(View.GONE); + mView.setVisibility(View.GONE); mOrientationHandle.setVisibility(View.VISIBLE); } } @@ -839,22 +847,22 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback // mOrientedHandle is initialized lazily mOrientationHandle.setVisibility(View.GONE); } - mNavigationBarView.setVisibility(View.VISIBLE); - mNavigationBarView.setOrientedHandleSamplingRegion(null); + mView.setVisibility(View.VISIBLE); + mView.setOrientedHandleSamplingRegion(null); } private void reconfigureHomeLongClick() { - if (mNavigationBarView.getHomeButton().getCurrentView() == null) { + if (mView.getHomeButton().getCurrentView() == null) { return; } if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) { - mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(false); - mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false); - mNavigationBarView.getHomeButton().setOnLongClickListener(null); + mView.getHomeButton().getCurrentView().setLongClickable(false); + mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false); + mView.getHomeButton().setOnLongClickListener(null); } else { - mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(true); - mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(true); - mNavigationBarView.getHomeButton().setOnLongClickListener(this::onHomeLongClick); + mView.getHomeButton().getCurrentView().setLongClickable(true); + mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(true); + mView.getHomeButton().setOnLongClickListener(this::onHomeLongClick); } } @@ -877,8 +885,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback pw.println(" mTransientShown=" + mTransientShown); pw.println(" mTransientShownFromGestureOnSystemBar=" + mTransientShownFromGestureOnSystemBar); - dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions()); - mNavigationBarView.dump(pw); + dumpBarTransitions(pw, "mNavigationBarView", mView.getBarTransitions()); + mView.dump(pw); } // ----- CommandQueue Callbacks ----- @@ -914,7 +922,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback orientSecondaryHomeHandle(); } if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state)); - mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); + mView.setWindowVisible(isNavBarWindowVisible()); } } @@ -923,12 +931,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback // The CommandQueue callbacks are added when the view is created to ensure we track other // states, but until the view is attached (at the next traversal), the view's display is // not valid. Just ignore the rotation in this case. - if (!mNavigationBarView.isAttachedToWindow()) return; + if (!mView.isAttachedToWindow()) return; final boolean rotateSuggestionsDisabled = RotationButtonController .hasDisable2RotateSuggestionFlag(mDisabledFlags2); final RotationButtonController rotationButtonController = - mNavigationBarView.getRotationButtonController(); + mView.getRotationButtonController(); final RotationButton rotationButton = rotationButtonController.getRotationButton(); if (RotationContextButton.DEBUG_ROTATION) { @@ -950,7 +958,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback if (running) { mNavbarOverlayController.setButtonState(/* visible */false, /* force */true); } - mNavigationBarView.getRotationButtonController().setRecentsAnimationRunning(running); + mView.getRotationButtonController().setRecentsAnimationRunning(running); } /** Restores the appearance and the transient saved state to {@link NavigationBar}. */ @@ -985,7 +993,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } if (mBehavior != behavior) { mBehavior = behavior; - mNavigationBarView.setBehavior(behavior); + mView.setBehavior(behavior); updateSystemUiStateFlags(); } } @@ -1026,7 +1034,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } private void handleTransientChanged() { - mNavigationBarView.onTransientStateChanged(mTransientShown, + mView.onTransientStateChanged(mTransientShown, mTransientShownFromGestureOnSystemBar); final int transitionMode = transitionMode(mTransientShown, mAppearance); if (updateTransitionMode(transitionMode) && mLightBarController != null) { @@ -1076,7 +1084,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback | StatusBarManager.DISABLE_SEARCH); if (masked != mDisabledFlags1) { mDisabledFlags1 = masked; - mNavigationBarView.setDisabledFlags(state1, mSysUiFlagsContainer); + mView.setDisabledFlags(state1, mSysUiFlagsContainer); updateScreenPinningGestures(); } @@ -1092,13 +1100,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback private void setDisabled2Flags(int state2) { // Method only called on change of disable2 flags - mNavigationBarView.getRotationButtonController().onDisable2FlagChanged(state2); + mView.getRotationButtonController().onDisable2FlagChanged(state2); } // ----- Internal stuff ----- private void refreshLayout(int layoutDirection) { - mNavigationBarView.setLayoutDirection(layoutDirection); + mView.setLayoutDirection(layoutDirection); } private boolean shouldDisableNavbarGestures() { @@ -1107,7 +1115,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } private void repositionNavigationBar(int rotation) { - if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return; + if (mView == null || !mView.isAttachedToWindow()) return; prepareNavigationBarView(); @@ -1117,10 +1125,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback private void updateScreenPinningGestures() { // Change the cancel pin gesture to home and back if recents button is invisible boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive(); - ButtonDispatcher backButton = mNavigationBarView.getBackButton(); - ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton(); + ButtonDispatcher backButton = mView.getBackButton(); + ButtonDispatcher recentsButton = mView.getRecentsButton(); if (pinningActive) { - boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible(); + boolean recentsVisible = mView.isRecentsButtonVisible(); backButton.setOnLongClickListener(recentsVisible ? this::onLongPressBackRecents : this::onLongPressBackHome); @@ -1135,27 +1143,27 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } private void notifyNavigationBarScreenOn() { - mNavigationBarView.updateNavButtonIcons(); + mView.updateNavButtonIcons(); } private void prepareNavigationBarView() { - mNavigationBarView.reorient(); + mView.reorient(); - ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton(); + ButtonDispatcher recentsButton = mView.getRecentsButton(); recentsButton.setOnClickListener(this::onRecentsClick); recentsButton.setOnTouchListener(this::onRecentsTouch); - ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); + ButtonDispatcher homeButton = mView.getHomeButton(); homeButton.setOnTouchListener(this::onHomeTouch); reconfigureHomeLongClick(); - ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); + ButtonDispatcher accessibilityButton = mView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); updateAccessibilityStateFlags(); - ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton(); + ButtonDispatcher imeSwitcherButton = mView.getImeSwitchButton(); imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick); updateScreenPinningGestures(); @@ -1212,7 +1220,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback @VisibleForTesting boolean onHomeLongClick(View v) { - if (!mNavigationBarView.isRecentsButtonVisible() + if (!mView.isRecentsButtonVisible() && ActivityManagerWrapper.getInstance().isScreenPinningActive()) { return onLongPressBackHome(v); } @@ -1227,7 +1235,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); mAssistManagerLazy.get().startAssist(args); mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::awakenDreams); - mNavigationBarView.abortCurrentGesture(); + mView.abortCurrentGesture(); return true; } @@ -1302,8 +1310,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback return true; } else if (v.getId() == btnId1) { ButtonDispatcher button = btnId2 == R.id.recent_apps - ? mNavigationBarView.getRecentsButton() - : mNavigationBarView.getHomeButton(); + ? mView.getRecentsButton() : mView.getHomeButton(); if (!button.getCurrentView().isPressed()) { // If we aren't pressing recents/home right now then they presses // won't be together, so send the standard long-press action. @@ -1323,15 +1330,14 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } else if (v.getId() == btnId2) { return btnId2 == R.id.recent_apps ? false - : onHomeLongClick( - mNavigationBarView.getHomeButton().getCurrentView()); + : onHomeLongClick(mView.getHomeButton().getCurrentView()); } } } finally { if (stopLockTaskMode) { activityManager.stopSystemLockTaskMode(); // When exiting refresh disabled flags. - mNavigationBarView.updateNavButtonIcons(); + mView.updateNavButtonIcons(); } } @@ -1363,11 +1369,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } void updateAccessibilityStateFlags() { - if (mNavigationBarView != null) { + if (mView != null) { int a11yFlags = mNavBarHelper.getA11yButtonState(); boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; - mNavigationBarView.setAccessibilityButtonState(clickable, longClickable); + mView.setAccessibilityButtonState(clickable, longClickable); } updateSystemUiStateFlags(); } @@ -1416,7 +1422,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mLightBarController = lightBarController; if (mLightBarController != null) { mLightBarController.setNavigationBar( - mNavigationBarView.getLightTransitionsController()); + mView.getLightTransitionsController()); } } @@ -1426,7 +1432,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback if (mAutoHideController != null) { mAutoHideController.setNavigationBar(mAutoHideUiElement); } - mNavigationBarView.setAutoHideController(autoHideController); + mView.setAutoHideController(autoHideController); } private boolean isTransientShown() { @@ -1458,11 +1464,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback mCentralSurfacesOptionalLazy.get().map(CentralSurfaces::isDeviceInteractive) .orElse(false) && mNavigationBarWindowState != WINDOW_STATE_HIDDEN; - mNavigationBarView.getBarTransitions().transitionTo(mTransitionMode, anim); + mView.getBarTransitions().transitionTo(mTransitionMode, anim); } public void disableAnimationsDuringHide(long delay) { - mNavigationBarView.setLayoutTransitionsEnabled(false); + mView.setLayoutTransitionsEnabled(false); mHandler.postDelayed(mEnableLayoutTransitions, delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); } @@ -1478,11 +1484,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } public NavigationBarTransitions getBarTransitions() { - return mNavigationBarView.getBarTransitions(); + return mView.getBarTransitions(); } public void finishBarAnimations() { - mNavigationBarView.getBarTransitions().finishAnimations(); + mView.getBarTransitions().finishAnimations(); } private WindowManager.LayoutParams getBarLayoutParams(int rotation) { @@ -1567,8 +1573,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback } private final Consumer<Integer> mRotationWatcher = rotation -> { - if (mNavigationBarView != null - && mNavigationBarView.needsReorient(rotation)) { + if (mView != null && mView.needsReorient(rotation)) { repositionNavigationBar(rotation); } }; @@ -1578,14 +1583,14 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback public void onReceive(Context context, Intent intent) { // TODO(193941146): Currently unregistering a receiver through BroadcastDispatcher is // async, but we've already cleared the fields. Just return early in this case. - if (mNavigationBarView == null) { + if (mView == null) { return; } String action = intent.getAction(); if (Intent.ACTION_SCREEN_OFF.equals(action) || Intent.ACTION_SCREEN_ON.equals(action)) { notifyNavigationBarScreenOn(); - mNavigationBarView.onScreenStateChanged(Intent.ACTION_SCREEN_ON.equals(action)); + mView.onScreenStateChanged(Intent.ACTION_SCREEN_ON.equals(action)); } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user @@ -1607,11 +1612,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback final boolean oldBackAlt = (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; if (newBackAlt != oldBackAlt) { - mNavigationBarView.onImeVisibilityChanged(newBackAlt); + mView.onImeVisibilityChanged(newBackAlt); mImeVisible = newBackAlt; } - mNavigationBarView.setNavigationIconHints(hints); + mView.setNavigationIconHints(hints); } if (DEBUG) { android.widget.Toast.makeText(mContext, @@ -1637,8 +1642,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback if (!canShowSecondaryHandle()) { resetSecondaryHandle(); } - if (mNavigationBarView != null) { - mNavigationBarView.setNavBarMode(mode); + if (mView != null) { + mView.setNavBarMode(mode); } } }; @@ -1671,14 +1676,14 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback switch (action) { case MotionEvent.ACTION_DOWN: // Allow gestures starting in the deadzone to be slippery - mNavigationBarView.setSlippery(true); + mView.setSlippery(true); mDeadZoneConsuming = true; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // When a gesture started in the deadzone is finished, restore // slippery state - mNavigationBarView.updateSlippery(); + mView.updateSlippery(); mDeadZoneConsuming = false; break; } @@ -1687,14 +1692,4 @@ public class NavigationBar implements View.OnAttachStateChangeListener, Callback return false; } }; - - - /** - * Injectable factory for construction a {@link NavigationBar}. - */ - @AssistedFactory - public interface Factory { - /** Construct a {@link NavigationBar} */ - NavigationBar create(Context context, WindowManager windowManager); - } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java new file mode 100644 index 000000000000..1d792af8ab59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.navigationbar; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Nullable; + +import com.android.systemui.dagger.qualifiers.DisplayId; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +import dagger.BindsInstance; +import dagger.Subcomponent; + +/** + * Subcomponent for a NavigationBar. + * + * Generally creatd on a per-display basis. + */ +@Subcomponent(modules = { NavigationBarModule.class }) +@NavigationBarComponent.NavigationBarScope +public interface NavigationBarComponent { + + /** Factory for {@link NavigationBarComponent}. */ + @Subcomponent.Factory + interface Factory { + NavigationBarComponent create( + @BindsInstance @DisplayId Context context, + @BindsInstance @Nullable Bundle savedState); + } + + /** */ + NavigationBar getNavigationBar(); + + /** + * Scope annotation for singleton items within the NavigationBarComponent. + */ + @Documented + @Retention(RUNTIME) + @Scope + @interface NavigationBarScope {} +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index b640e1d24385..ade86ae76db4 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -39,7 +39,6 @@ import android.util.SparseArray; import android.view.Display; import android.view.IWindowManager; import android.view.View; -import android.view.WindowManager; import android.view.WindowManagerGlobal; import androidx.annotation.NonNull; @@ -84,7 +83,7 @@ public class NavigationBarController implements private final Context mContext; private final Handler mHandler; - private final NavigationBar.Factory mNavigationBarFactory; + private final NavigationBarComponent.Factory mNavigationBarComponentFactory; private final DisplayManager mDisplayManager; private final TaskbarDelegate mTaskbarDelegate; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -110,7 +109,7 @@ public class NavigationBarController implements ConfigurationController configurationController, NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, - NavigationBar.Factory navigationBarFactory, + NavigationBarComponent.Factory navigationBarComponentFactory, StatusBarKeyguardViewManager statusBarKeyguardViewManager, DumpManager dumpManager, AutoHideController autoHideController, @@ -119,7 +118,7 @@ public class NavigationBarController implements Optional<BackAnimation> backAnimation) { mContext = context; mHandler = mainHandler; - mNavigationBarFactory = navigationBarFactory; + mNavigationBarComponentFactory = navigationBarComponentFactory; mDisplayManager = mContext.getSystemService(DisplayManager.class); commandQueue.addCallback(this); configurationController.addCallback(this); @@ -324,14 +323,13 @@ public class NavigationBarController implements final Context context = isOnDefaultDisplay ? mContext : mContext.createDisplayContext(display); - NavigationBar navBar = mNavigationBarFactory.create( - context, context.getSystemService(WindowManager.class)); - + NavigationBarComponent component = mNavigationBarComponentFactory.create( + context, savedState); + NavigationBar navBar = component.getNavigationBar(); + navBar.init(); mNavigationBars.put(displayId, navBar); - boolean navBarVisible = mStatusBarKeyguardViewManager.isNavBarVisible(); - View navigationBarView = navBar.createView(savedState, navBarVisible); - navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (result != null) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java new file mode 100644 index 000000000000..93bf136e88f1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.navigationbar; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.DisplayId; +import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; + +import dagger.Module; +import dagger.Provides; + +/** Module for {@link com.android.systemui.navigationbar.NavigationBarComponent}. */ +@Module +public interface NavigationBarModule { + /** A Layout inflater specific to the display's context. */ + @Provides + @NavigationBarScope + @DisplayId + static LayoutInflater provideLayoutInflater(@DisplayId Context context) { + return LayoutInflater.from(context); + } + + /** */ + @Provides + @NavigationBarScope + static NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) { + return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null); + } + + /** */ + @Provides + @NavigationBarScope + static NavigationBarView provideNavigationBarview( + @DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) { + View barView = layoutInflater.inflate(R.layout.navigation_bar, frame); + return barView.findViewById(R.id.navigation_bar_view); + } + + /** A WindowManager specific to the display's context. */ + @Provides + @NavigationBarScope + @DisplayId + static WindowManager provideWindowManager(@DisplayId Context context) { + return context.getSystemService(WindowManager.class); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java index 7fb58f0d8fc6..9305d0518732 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java @@ -30,6 +30,8 @@ import com.android.systemui.R; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; +import javax.inject.Inject; + /** * The "dead zone" consumes unintentional taps along the top edge of the navigation bar. * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and @@ -82,6 +84,7 @@ public class DeadZone { } }; + @Inject public DeadZone(NavigationBarView view) { mNavigationBarView = view; mNavBarController = Dependency.get(NavigationBarController.class); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 2ac34b22be5b..369a552a934a 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -271,6 +271,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mPlaySound = false; return; } + if (!showLowBatteryNotification()) { + return; + } final int warningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); @@ -323,6 +326,29 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { return isSevereState && mUseSevereDialog; } + /** + * Disable low battery warning notification if battery saver schedule mode set as + * "Based on percentage". + * + * return {@code false} if scheduled by percentage. + */ + private boolean showLowBatteryNotification() { + final ContentResolver resolver = mContext.getContentResolver(); + final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, + PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); + + // Return true if battery saver schedule mode will not trigger by percentage. + if (mode != PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) { + return true; + } + + // Return true if battery saver mode trigger percentage is less than 0, which means it is + // set as "Based on routine" mode, otherwise it will be "Based on percentage" mode. + final int threshold = + Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); + return threshold <= 0; + } + private void showAutoSaverSuggestionNotification() { final CharSequence message = mContext.getString(R.string.auto_saver_text); final Notification.Builder nb = diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 27da6f3d2d2c..842a1b92f690 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -247,18 +247,7 @@ internal class FooterActionsController @Inject constructor( } fun setExpansion(headerExpansionFraction: Float) { - if (featureFlags.isEnabled(Flags.NEW_FOOTER)) { - if (headerExpansionFraction != lastExpansion) { - if (headerExpansionFraction >= 1f) { - mView.animate().alpha(1f).setDuration(500L).start() - } else if (lastExpansion >= 1f && headerExpansionFraction < 1f) { - mView.animate().alpha(0f).setDuration(250L).start() - } - lastExpansion = headerExpansionFraction - } - } else { - alphaAnimator.setPosition(headerExpansionFraction) - } + alphaAnimator.setPosition(headerExpansionFraction) } fun setKeyguardShowing(showing: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index f8680552f90a..ceb895f74d90 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -139,13 +139,17 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } void updateResources(QSPanelController qsPanelController, - QuickStatusBarHeaderController quickStatusBarHeaderController) { + QuickStatusBarHeaderController quickStatusBarHeaderController, + boolean newFooter) { + int bottomPadding = 0; + if (newFooter) { + bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); + } mQSPanelContainer.setPaddingRelative( mQSPanelContainer.getPaddingStart(), Utils.getQsHeaderSystemIconsAreaHeight(mContext), mQSPanelContainer.getPaddingEnd(), - mQSPanelContainer.getPaddingBottom() - ); + bottomPadding); int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); int padding = getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index 7d61991c910a..61da18224023 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -18,6 +18,8 @@ package com.android.systemui.qs; import android.content.res.Configuration; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; @@ -30,23 +32,26 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { private final QSPanelController mQsPanelController; private final QuickStatusBarHeaderController mQuickStatusBarHeaderController; private final ConfigurationController mConfigurationController; + private final boolean mNewFooter; private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override public void onConfigChanged(Configuration newConfig) { - mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController); + mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter); } }; @Inject QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController, - ConfigurationController configurationController) { + ConfigurationController configurationController, + FeatureFlags featureFlags) { super(view); mQsPanelController = qsPanelController; mQuickStatusBarHeaderController = quickStatusBarHeaderController; mConfigurationController = configurationController; + mNewFooter = featureFlags.isEnabled(Flags.NEW_FOOTER); } @Override @@ -60,7 +65,7 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { @Override protected void onViewAttached() { - mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController); + mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter); mConfigurationController.addCallback(mConfigurationListener); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index fe8c309ad2f3..4e631046063b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -599,6 +599,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSPanelScrollView.getHeight()); } mQSPanelScrollView.setClipBounds(mQsBounds); + + mQsMediaHost.getCurrentClipping().set(0, 0, getView().getMeasuredWidth(), + mQSPanelScrollView.getMeasuredHeight() - mQSPanelScrollView.getPaddingBottom()); } private void updateMediaPositions() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index b04d75273831..11a36ada4e36 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -374,7 +374,7 @@ public class QSPanel extends LinearLayout implements Tunable { setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), - mUseNewFooter ? res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom) : 0); + getPaddingEnd()); } void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 865f09337fa3..dd2929c9a67a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -33,7 +33,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.media.MediaFlags; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; import com.android.systemui.media.MediaHostState; @@ -46,7 +45,6 @@ import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.Utils; import javax.inject.Inject; import javax.inject.Named; @@ -65,7 +63,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final FalsingManager mFalsingManager; private final BrightnessController mBrightnessController; private final BrightnessSliderController mBrightnessSliderController; - private final MediaFlags mMediaFlags; private final BrightnessMirrorHandler mBrightnessMirrorHandler; private final FeatureFlags mFeatureFlags; @@ -75,7 +72,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { new QSPanel.OnConfigurationChangedListener() { @Override public void onConfigurationChange(Configuration newConfig) { - updateMediaExpansion(); mView.updateResources(); mQsSecurityFooter.onConfigurationChanged(); if (mView.isListening()) { @@ -105,8 +101,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSliderController.Factory brightnessSliderFactory, - FalsingManager falsingManager, FeatureFlags featureFlags, - MediaFlags mediaFlags) { + FalsingManager falsingManager, FeatureFlags featureFlags) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager); mQSFgsManagerFooter = qsFgsManagerFooter; @@ -117,7 +112,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mFalsingManager = falsingManager; mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView); - mMediaFlags = mediaFlags; mView.setBrightnessView(mBrightnessSliderController.getRootView()); mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController); @@ -129,7 +123,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override public void onInit() { super.onInit(); - updateMediaExpansion(); + mMediaHost.setExpansion(MediaHostState.EXPANDED); mMediaHost.setShowsOnlyActiveMedia(false); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); mQsCustomizerController.init(); @@ -137,17 +131,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mQSFgsManagerFooter.init(); } - private void updateMediaExpansion() { - boolean inSplitShade = Utils.shouldUseSplitNotificationShade(getResources()); - float expansion; - if (inSplitShade && !mMediaFlags.useMediaSessionLayout()) { - expansion = MediaHostState.COLLAPSED; - } else { - expansion = MediaHostState.EXPANDED; - } - mMediaHost.setExpansion(expansion); - } - @Override protected void onViewAttached() { super.onViewAttached(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index b2e008b9d2bc..c6ebd732518c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -26,7 +26,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; -import com.android.systemui.media.MediaFlags; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.qs.QSTile; @@ -53,7 +52,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> } }; - private final MediaFlags mMediaFlags; private final boolean mUsingCollapsedLandscapeMedia; @Inject @@ -62,14 +60,12 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, @Named(QUICK_QS_PANEL) MediaHost mediaHost, @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia, - MediaFlags mediaFlags, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager); mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia; - mMediaFlags = mediaFlags; } @Override @@ -84,8 +80,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> int rotation = getRotation(); boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE || rotation == RotationUtils.ROTATION_SEASCAPE; - if (mMediaFlags.useMediaSessionLayout() - && (!mUsingCollapsedLandscapeMedia || !isLandscape)) { + if (!mUsingCollapsedLandscapeMedia || !isLandscape) { mMediaHost.setExpansion(MediaHost.EXPANDED); } else { mMediaHost.setExpansion(MediaHost.COLLAPSED); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index 7fb9ef34cfd1..be6982ab2470 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -16,13 +16,14 @@ package com.android.systemui.qs.tileimpl; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; +import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Color; import android.graphics.drawable.Animatable2; import android.graphics.drawable.Animatable2.AnimationCallback; import android.graphics.drawable.Drawable; @@ -53,6 +54,8 @@ public class QSIconViewImpl extends QSIconView { @Nullable private QSTile.Icon mLastIcon; + private ValueAnimator mColorAnimator = new ValueAnimator(); + public QSIconViewImpl(Context context) { super(context); @@ -61,6 +64,7 @@ public class QSIconViewImpl extends QSIconView { mIcon = createIcon(); addView(mIcon); + mColorAnimator.setDuration(QS_ANIM_LENGTH); } @Override @@ -165,7 +169,6 @@ public class QSIconViewImpl extends QSIconView { mState = state.state; if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); - mTint = color; } else { if (iv instanceof AlphaControlledSlashImageView) { ((AlphaControlledSlashImageView)iv) @@ -173,7 +176,6 @@ public class QSIconViewImpl extends QSIconView { } else { setTint(iv, color); } - mTint = color; updateIcon(iv, state, allowAnimations); } } else { @@ -191,39 +193,30 @@ public class QSIconViewImpl extends QSIconView { ((AlphaControlledSlashImageView)iv) .setFinalImageTintList(ColorStateList.valueOf(toColor)); } + mColorAnimator.cancel(); if (mAnimationEnabled && ValueAnimator.areAnimatorsEnabled()) { - final float fromAlpha = Color.alpha(fromColor); - final float toAlpha = Color.alpha(toColor); - final float fromChannel = Color.red(fromColor); - final float toChannel = Color.red(toColor); - - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - anim.setDuration(QS_ANIM_LENGTH); - anim.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction); - int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction); - - setTint(iv, Color.argb(alpha, channel, channel, channel)); - }); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - endRunnable.run(); - } + PropertyValuesHolder values = PropertyValuesHolder.ofInt("color", fromColor, toColor); + values.setEvaluator(ArgbEvaluator.getInstance()); + mColorAnimator.setValues(values); + mColorAnimator.removeAllListeners(); + mColorAnimator.addUpdateListener(animation -> { + setTint(iv, (int) animation.getAnimatedValue()); }); - anim.start(); + mColorAnimator.addListener(new EndRunnableAnimatorListener(endRunnable)); + + mColorAnimator.start(); } else { + setTint(iv, toColor); endRunnable.run(); } } - public static void setTint(ImageView iv, int color) { + public void setTint(ImageView iv, int color) { iv.setImageTintList(ColorStateList.valueOf(color)); + mTint = color; } - protected int getIconMeasureMode() { return MeasureSpec.EXACTLY; } @@ -261,4 +254,25 @@ public class QSIconViewImpl extends QSIconView { return 0; } } + + private static class EndRunnableAnimatorListener extends AnimatorListenerAdapter { + private Runnable mRunnable; + + EndRunnableAnimatorListener(Runnable endRunnable) { + super(); + mRunnable = endRunnable; + } + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + mRunnable.run(); + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mRunnable.run(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index 3c6ab34733e5..bbba0071094b 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -199,7 +199,7 @@ public class ScrimDrawable extends Drawable { drawConcave(canvas); } else if (mCornerRadiusEnabled && mCornerRadius > 0) { canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right, - getBounds().bottom + mCornerRadius, + getBounds().bottom, /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint); } else { canvas.drawRect(getBounds().left, getBounds().top, getBounds().right, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index d51aaad46432..55ca8f3fd634 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -575,13 +575,7 @@ public class KeyguardIndicationController { return; } int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); - try { - mIActivityManager.switchUser(UserHandle.USER_SYSTEM); - mIActivityManager.stopUser(currentUserId, true /* force */, - null); - } catch (RemoteException re) { - Log.e(TAG, "Failed to logout user", re); - } + mDevicePolicyManager.logoutUser(); }) .build(), false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index de3e89d6dc8c..4732a8c09500 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -34,11 +34,11 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.phone.ScrimController -import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.Utils import java.io.FileDescriptor diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index 3fe108f2c951..f0e01a33fc99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -192,7 +192,10 @@ constructor( override fun onTouchEvent(event: MotionEvent): Boolean { val finishExpanding = (event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP) && isExpanding - if (!canHandleMotionEvent() && !finishExpanding) { + + val isDraggingNotificationOrCanBypass = mStartingChild?.showingPulsing() == true || + bypassController.canBypass() + if ((!canHandleMotionEvent() || !isDraggingNotificationOrCanBypass) && !finishExpanding) { // We allow cancellations/finishing to still go through here to clean up the state return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 1237c70fcbe3..aac5b8d6b2eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -70,7 +70,7 @@ public class KeyguardCoordinator implements Coordinator { private final NotifFilter mNotifFilter = new NotifFilter(TAG) { @Override public boolean shouldFilterOut(NotificationEntry entry, long now) { - return mKeyguardNotificationVisibilityProvider.hideNotification(entry); + return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 699c4e77321e..acc493d4b992 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -254,10 +254,14 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable, @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("pipelineRunAllowed: " + mPipelineRunAllowed); + pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing); + pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity); pw.println("reorderingAllowed: " + mReorderingAllowed); pw.println(" screenOn: " + mScreenOn); pw.println(" panelExpanded: " + mPanelExpanded); pw.println(" pulsing: " + mPulsing); + pw.println("isSuppressingPipelineRun: " + mIsSuppressingPipelineRun); pw.println("isSuppressingGroupChange: " + mIsSuppressingGroupChange); pw.println("isSuppressingEntryReorder: " + mIsSuppressingEntryReorder); pw.println("entriesWithSuppressedSectionChange: " 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 efe88e6d4f0a..34c8044ef0d3 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 @@ -73,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; +import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProviderModule; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -107,9 +108,10 @@ import dagger.Provides; */ @Module(includes = { CoordinatorsModule.class, + KeyguardNotificationVisibilityProviderModule.class, NotifActivityLaunchEventsModule.class, - NotifPipelineChoreographerModule.class, NotifPanelEventsModule.class, + NotifPipelineChoreographerModule.class, NotificationSectionHeadersModule.class, }) public interface NotificationsModule { 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 70c9a16b9d7a..ff0e47f3cfec 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 @@ -14,6 +14,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -22,13 +23,49 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ListenerSet +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SecureSettings +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import java.util.function.Consumer import javax.inject.Inject -/** - * Determines if notifications should be visible based on the state of the keyguard - */ -class KeyguardNotificationVisibilityProvider @Inject constructor( +/** Determines if notifications should be visible based on the state of the keyguard. */ +interface KeyguardNotificationVisibilityProvider { + /** + * Determines if the given notification should be hidden based on the current keyguard state. + * If a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this + * method may no longer be valid and should be re-queried. + */ + fun shouldHideNotification(entry: NotificationEntry): Boolean + + /** Registers a listener to be notified when the internal keyguard state has been updated. */ + fun addOnStateChangedListener(listener: Consumer<String>) + + /** Unregisters a listener previously registered with [addOnStateChangedListener]. */ + fun removeOnStateChangedListener(listener: Consumer<String>) +} + +/** Provides a [KeyguardNotificationVisibilityProvider] in [SysUISingleton] scope. */ +@Module(includes = [KeyguardNotificationVisibilityProviderImplModule::class]) +object KeyguardNotificationVisibilityProviderModule + +@Module +private interface KeyguardNotificationVisibilityProviderImplModule { + @Binds + fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl): + KeyguardNotificationVisibilityProvider + + @Binds + @IntoMap + @ClassKey(KeyguardNotificationVisibilityProvider::class) + fun bindStartable(impl: KeyguardNotificationVisibilityProviderImpl): CoreStartable +} + +@SysUISingleton +private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( context: Context, @Main private val handler: Handler, private val keyguardStateController: KeyguardStateController, @@ -36,8 +73,10 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val highPriorityProvider: HighPriorityProvider, private val statusBarStateController: StatusBarStateController, - private val broadcastDispatcher: BroadcastDispatcher -) : CoreStartable(context) { + private val broadcastDispatcher: BroadcastDispatcher, + private val secureSettings: SecureSettings, + private val globalSettings: GlobalSettings +) : CoreStartable(context), KeyguardNotificationVisibilityProvider { private val onStateChangedListeners = ListenerSet<Consumer<String>>() private var hideSilentNotificationsOnLockscreen: Boolean = false @@ -60,33 +99,28 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( // register lockscreen settings changed callbacks: val settingsObserver: ContentObserver = object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean, uri: Uri) { + override fun onChange(selfChange: Boolean, uri: Uri?) { if (keyguardStateController.isShowing) { notifyStateChanged("Settings $uri changed") } } } - mContext.contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), - false, + secureSettings.registerContentObserverForUser( + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, settingsObserver, UserHandle.USER_ALL) - mContext.contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + secureSettings.registerContentObserverForUser( + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true, settingsObserver, UserHandle.USER_ALL) - mContext.contentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ZEN_MODE), - false, - settingsObserver) + globalSettings.registerContentObserver(Settings.Global.ZEN_MODE, settingsObserver) - mContext.contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS), - false, + secureSettings.registerContentObserverForUser( + Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, settingsObserver, UserHandle.USER_ALL) @@ -98,41 +132,36 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( }) broadcastDispatcher.registerReceiver(object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - if (keyguardStateController.isShowing()) { + if (keyguardStateController.isShowing) { // maybe public mode changed - notifyStateChanged(intent.action) + notifyStateChanged(intent.action!!) } } }, IntentFilter(Intent.ACTION_USER_SWITCHED)) } - fun addOnStateChangedListener(listener: Consumer<String>) { + override fun addOnStateChangedListener(listener: Consumer<String>) { onStateChangedListeners.addIfAbsent(listener) } - fun removeOnStateChangedListener(listener: Consumer<String>) { + override fun removeOnStateChangedListener(listener: Consumer<String>) { onStateChangedListeners.remove(listener) } private fun notifyStateChanged(reason: String) { - onStateChangedListeners.forEach({ it.accept(reason) }) + onStateChangedListeners.forEach { it.accept(reason) } } - /** - * Determines if the given notification should be hidden based on the current keyguard state. - * If Listener#onKeyguardStateChanged is invoked, the results of this method may no longer - * be valid, and so should be re-queried - */ - fun hideNotification(entry: NotificationEntry): Boolean { + override fun shouldHideNotification(entry: NotificationEntry): Boolean { val sbn = entry.sbn // FILTER OUT the notification when the keyguard is showing and... - if (keyguardStateController.isShowing()) { + if (keyguardStateController.isShowing) { // ... user settings or the device policy manager doesn't allow lockscreen // notifications; if (!lockscreenUserManager.shouldShowLockscreenNotifications()) { return true } - val currUserId: Int = lockscreenUserManager.getCurrentUserId() + val currUserId: Int = lockscreenUserManager.currentUserId val notifUserId = if (sbn.user.identifier == UserHandle.USER_ALL) currUserId else sbn.user.identifier @@ -178,9 +207,7 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( } private fun readShowSilentNotificationSetting() { - hideSilentNotificationsOnLockscreen = Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, - 1) == 0 + hideSilentNotificationsOnLockscreen = + secureSettings.getBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index c1771ccce9d0..e210f193b0a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -312,7 +312,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } - if (mKeyguardNotificationVisibilityProvider.hideNotification(entry)) { + if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) { mLogger.keyguardHideNotification(entry.getKey()); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java index 4b0e2ffd5d7f..41eeada0fcda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java @@ -21,7 +21,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.util.Log; -import java.io.IOException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -82,16 +81,8 @@ public class NotificationInlineImageCache implements NotificationInlineImageReso @Override protected Drawable doInBackground(Uri... uris) { - Drawable drawable = null; Uri target = uris[0]; - - try { - drawable = mResolver.resolveImage(target); - } catch (IOException | SecurityException ex) { - Log.d(TAG, "PreloadImageTask: Resolve failed from " + target, ex); - } - - return drawable; + return mResolver.resolveImage(target); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index 44ccb68cce4a..b05e64ab1991 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -31,7 +31,6 @@ import com.android.internal.widget.ImageResolver; import com.android.internal.widget.LocalImageResolver; import com.android.internal.widget.MessagingMessage; -import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -111,30 +110,30 @@ public class NotificationInlineImageResolver implements ImageResolver { * To resolve image from specified uri directly. If the resulting image is larger than the * maximum allowed size, scale it down. * @param uri Uri of the image. - * @return Drawable of the image. - * @throws IOException Throws if failed at resolving the image. + * @return Drawable of the image, or null if unable to load. */ - Drawable resolveImage(Uri uri) throws IOException { - return LocalImageResolver.resolveImage(uri, mContext, mMaxImageWidth, mMaxImageHeight); + Drawable resolveImage(Uri uri) { + try { + return LocalImageResolver.resolveImage(uri, mContext, mMaxImageWidth, mMaxImageHeight); + } catch (Exception ex) { + // Catch general Exception because ContentResolver can re-throw arbitrary Exception + // from remote process as a RuntimeException. See: Parcel#readException + Log.d(TAG, "resolveImage: Can't load image from " + uri, ex); + } + return null; } @Override public Drawable loadImage(Uri uri) { - Drawable result = null; - try { - if (hasCache()) { - // if the uri isn't currently cached, try caching it first - if (!mImageCache.hasEntry(uri)) { - mImageCache.preload((uri)); - } - result = mImageCache.get(uri); - } else { - result = resolveImage(uri); - } - } catch (IOException | SecurityException ex) { - Log.d(TAG, "loadImage: Can't load image from " + uri, ex); + return hasCache() ? loadImageFromCache(uri) : resolveImage(uri); + } + + private Drawable loadImageFromCache(Uri uri) { + // if the uri isn't currently cached, try caching it first + if (!mImageCache.hasEntry(uri)) { + mImageCache.preload((uri)); } - return result; + return mImageCache.get(uri); } /** 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 c89f4d797819..5c68559ffb1e 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 @@ -689,7 +689,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0) && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD - && mQsExpansionFraction != 1 + // quick settings don't affect notifications when not in full screen + && (mQsExpansionFraction != 1 || !mQsFullScreen) && !mScreenOffAnimationController.shouldHideNotificationsFooter() && !mIsRemoteInputActive; boolean showHistory = mController.isHistoryEnabled(); @@ -799,6 +800,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable y = (int) (mAmbientState.getStackY() + mKeyguardNotificationAvailableSpace); drawDebugInfo(canvas, y, Color.RED, /* label= */ "mAmbientState.getStackY() + mKeyguardNotificationAvailableSpace = " + y); + + drawDebugInfo(canvas, mRoundedRectClippingBottom, Color.DKGRAY, + /* label= */ "mRoundedRectClippingBottom) = " + y); } private void drawDebugInfo(Canvas canvas, int y, int color, String label) { @@ -1311,7 +1315,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mOnStackYChanged != null) { mOnStackYChanged.accept(listenerNeedsAnimation); } - if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) { + if ((mQsExpansionFraction <= 0 || !mQsFullScreen) && !shouldSkipHeightUpdate()) { final float endHeight = updateStackEndHeight(); updateStackHeight(endHeight, fraction); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index ec2d608b2683..c2c8bd3fad5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -1695,12 +1695,20 @@ public class CentralSurfaces extends CoreStartable implements public void startActivity(Intent intent, boolean dismissShade, @Nullable ActivityLaunchAnimator.Controller animationController, boolean showOverLockscreenWhenLocked) { + startActivity(intent, dismissShade, animationController, showOverLockscreenWhenLocked, + UserHandle.CURRENT); + } + + @Override + public void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController, + boolean showOverLockscreenWhenLocked, UserHandle userHandle) { // Make sure that we dismiss the keyguard if it is directly dismissable or when we don't // want to show the activity above it. if (mKeyguardStateController.isUnlocked() || !showOverLockscreenWhenLocked) { startActivityDismissingKeyguard(intent, false, dismissShade, - false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, - 0 /* flags */, animationController); + false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, + 0 /* flags */, animationController, userHandle); return; } @@ -1755,7 +1763,7 @@ public class CentralSurfaces extends CoreStartable implements .create(mContext) .addNextIntent(intent) .startActivities(getActivityOptions(getDisplayId(), adapter), - UserHandle.CURRENT)); + userHandle)); } /** @@ -1775,7 +1783,7 @@ public class CentralSurfaces extends CoreStartable implements public void startActivity(Intent intent, boolean dismissShade, Callback callback) { startActivityDismissingKeyguard(intent, false, dismissShade, false /* disallowEnterPictureInPictureWhileLaunching */, callback, 0, - null /* animationController */); + null /* animationController */, UserHandle.CURRENT); } public void setQsExpanded(boolean expanded) { @@ -1869,13 +1877,7 @@ public class CentralSurfaces extends CoreStartable implements if (!mPresenter.isCollapsing()) { onClosingFinished(); } - - // Collapse the panel if we're launching in fullscreen, over the lockscreen. Do not do this - // if the device has gone back to sleep - through a horrific chain of 15 or so function - // calls, instantCollapseNotificationPanel will eventually call through to - // StatusBar#wakeUpIfDozing, which will wake the device up even if it was put to sleep - // during the launch animation. - if (launchIsFullScreen && mPowerManager.isInteractive()) { + if (launchIsFullScreen) { instantCollapseNotificationPanel(); } } @@ -2417,7 +2419,7 @@ public class CentralSurfaces extends CoreStartable implements boolean dismissShade, int flags) { startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, - flags, null /* animationController */); + flags, null /* animationController */, UserHandle.CURRENT); } public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, @@ -2428,7 +2430,8 @@ public class CentralSurfaces extends CoreStartable implements void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching, final Callback callback, int flags, - @Nullable ActivityLaunchAnimator.Controller animationController) { + @Nullable ActivityLaunchAnimator.Controller animationController, + final UserHandle userHandle) { if (onlyProvisioned && !mDeviceProvisionedController.isDeviceProvisioned()) return; final boolean willLaunchResolverActivity = @@ -2487,7 +2490,7 @@ public class CentralSurfaces extends CoreStartable implements intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, - options.toBundle(), UserHandle.CURRENT.getIdentifier()); + options.toBundle(), userHandle.getIdentifier()); } catch (RemoteException e) { Log.w(TAG, "Unable to start activity", e); } @@ -2860,7 +2863,8 @@ public class CentralSurfaces extends CoreStartable implements false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0 /* flags */, - animationController), + animationController, + UserHandle.CURRENT), delay); } @@ -3126,6 +3130,10 @@ public class CentralSurfaces extends CoreStartable implements public void finishKeyguardFadingAway() { mKeyguardStateController.notifyKeyguardDoneFading(); mScrimController.setExpansionAffectsAlpha(true); + + // If the device was re-locked while unlocking, we might have a pending lock that was + // delayed because the keyguard was in the middle of going away. + mKeyguardViewMediator.maybeHandlePendingLock(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 536be1c1a866..c4e655a45ca6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -371,7 +371,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent, false /* onlyProvisioned */, true /* dismissShade */, true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0, - null /* animationController */); + null /* animationController */, UserHandle.CURRENT); } else { if (!mCentralSurfaces.isDeviceInteractive()) { // Avoid flickering of the scrim when we instant launch the camera and the bouncer @@ -428,7 +428,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mCentralSurfaces.startActivityDismissingKeyguard(emergencyIntent, false /* onlyProvisioned */, true /* dismissShade */, true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0, - null /* animationController */); + null /* animationController */, UserHandle.CURRENT); return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java index 324d47eed1db..799e5feb1586 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.DejankUtils.whitelistIpcs; import android.content.Intent; +import android.os.UserHandle; import android.os.UserManager; import android.view.View; import android.view.ViewGroup; @@ -62,7 +63,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { mActivityStarter.startActivity(intent, true /* dismissShade */, ActivityLaunchAnimator.Controller.fromView(v, null), - true /* showOverlockscreenwhenlocked */); + true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM); } else { mUserSwitchDialogController.showDialog(v); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 5746ffb6debe..6cca9045d867 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1098,6 +1098,8 @@ public class NotificationPanelViewController extends PanelViewController { if (splitNotificationShadeChanged) { updateClockAppearance(); + updateQsState(); + mNotificationStackScrollLayoutController.updateFooter(); } } @@ -2348,7 +2350,7 @@ public class NotificationPanelViewController extends PanelViewController { private int calculateBottomQsClippingBound(int top) { if (mShouldUseSplitNotificationShade) { return top + mNotificationStackScrollLayoutController.getHeight() - - mSplitShadeNotificationsScrimMarginBottom; + + mSplitShadeNotificationsScrimMarginBottom; } else { return getView().getBottom(); } @@ -2466,7 +2468,12 @@ public class NotificationPanelViewController extends PanelViewController { // be visible, otherwise you can see the bounds once swiping up to see bouncer mScrimController.setNotificationsBounds(0, 0, 0, 0); } else { - mScrimController.setNotificationsBounds(left, top, right, bottom); + // Increase the height of the notifications scrim when not in split shade + // (e.g. portrait tablet) so the rounded corners are not visible at the bottom, + // in this case they are rendered off-screen + final int notificationsScrimBottom = + mShouldUseSplitNotificationShade ? bottom : bottom + radius; + mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom); } if (mShouldUseSplitNotificationShade) { @@ -2476,10 +2483,13 @@ public class NotificationPanelViewController extends PanelViewController { } mScrimController.setScrimCornerRadius(radius); + + // Convert global clipping coordinates to local ones, + // relative to NotificationStackScrollLayout int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft(); int nsslRight = right - mNotificationStackScrollLayoutController.getLeft(); int nsslTop = top - mNotificationStackScrollLayoutController.getTop(); - int nsslBottom = bottom; + int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop(); int bottomRadius = mShouldUseSplitNotificationShade ? radius : 0; mNotificationStackScrollLayoutController.setRoundedClippingBounds( nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt index 7764d33856ca..a15feeb9cda8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt @@ -4,6 +4,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowInsets +import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END @@ -11,6 +12,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.navigationbar.NavigationModeController @@ -21,14 +23,19 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.util.Utils import com.android.systemui.util.ViewController +import com.android.systemui.util.concurrency.DelayableExecutor import java.util.function.Consumer import javax.inject.Inject +@VisibleForTesting +internal const val INSET_DEBOUNCE_MILLIS = 500L + class NotificationsQSContainerController @Inject constructor( view: NotificationsQuickSettingsContainer, private val navigationModeController: NavigationModeController, private val overviewProxyService: OverviewProxyService, - private val featureFlags: FeatureFlags + private val featureFlags: FeatureFlags, + @Main private val delayableExecutor: DelayableExecutor ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { var qsExpanded = false @@ -60,11 +67,29 @@ class NotificationsQSContainerController @Inject constructor( taskbarVisible = visible } } - private val windowInsetsListener: Consumer<WindowInsets> = Consumer { insets -> - // when taskbar is visible, stableInsetBottom will include its height - bottomStableInsets = insets.stableInsetBottom - bottomCutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0 - updateBottomSpacing() + + // With certain configuration changes (like light/dark changes), the nav bar will disappear + // for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value + // for 500ms. + // All interactions with this object happen in the main thread. + private val delayedInsetSetter = object : Runnable, Consumer<WindowInsets> { + private var canceller: Runnable? = null + private var stableInsets = 0 + private var cutoutInsets = 0 + + override fun accept(insets: WindowInsets) { + // when taskbar is visible, stableInsetBottom will include its height + stableInsets = insets.stableInsetBottom + cutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0 + canceller?.run() + canceller = delayableExecutor.executeDelayed(this, INSET_DEBOUNCE_MILLIS) + } + + override fun run() { + bottomStableInsets = stableInsets + bottomCutoutInsets = cutoutInsets + updateBottomSpacing() + } } override fun onInit() { @@ -77,7 +102,7 @@ class NotificationsQSContainerController @Inject constructor( public override fun onViewAttached() { updateResources() overviewProxyService.addCallback(taskbarVisibilityListener) - mView.setInsetsChangedListener(windowInsetsListener) + mView.setInsetsChangedListener(delayedInsetSetter) mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } mView.setConfigurationChangedListener { updateResources() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index c160c22203a5..23a1087edfd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1016,6 +1016,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) { mNotificationShadeWindowController.setBouncerShowing(bouncerShowing); mCentralSurfaces.setBouncerShowing(bouncerShowing); + mKeyguardMessageAreaController.setBouncerShowing(bouncerShowing); } if (occluded != mLastOccluded || mFirstUpdate) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 79d646cdbd13..d26b378650e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -63,6 +63,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh // TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on. private static final String FLAG_TABLET_DIALOG_WIDTH = "persist.systemui.flag_tablet_dialog_width"; + private static final int DEFAULT_THEME = R.style.Theme_SystemUI_Dialog; + private static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true; private final Context mContext; @Nullable private final DismissReceiver mDismissReceiver; @@ -78,11 +80,15 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private List<Runnable> mOnCreateRunnables = new ArrayList<>(); public SystemUIDialog(Context context) { - this(context, R.style.Theme_SystemUI_Dialog); + this(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK); } public SystemUIDialog(Context context, int theme) { - this(context, theme, true /* dismissOnDeviceLock */); + this(context, theme, DEFAULT_DISMISS_ON_DEVICE_LOCK); + } + + public SystemUIDialog(Context context, boolean dismissOnDeviceLock) { + this(context, DEFAULT_THEME, dismissOnDeviceLock); } public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt index 909261f0eb7e..62549a70897b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone.userswitcher import android.content.Intent +import android.os.UserHandle import android.view.View import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.flags.FeatureFlags @@ -67,7 +68,7 @@ class StatusBarUserSwitcherControllerImpl @Inject constructor( activityStarter.startActivity(intent, true /* dismissShade */, ActivityLaunchAnimator.Controller.fromView(view, null), - true /* showOverlockscreenwhenlocked */) + true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM) } else { userSwitcherDialogController.showDialog(view) } 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 4d6d05f2b628..006edcaf41de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -903,11 +903,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene this.radius = radius; } - Animator createCircularRevealAnimator(View view) { + Animator createCircularHideAnimator(View view) { return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0); } - Animator createCircularHideAnimator(View view) { + Animator createCircularRevealAnimator(View view) { return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index eb8c247ca956..ca5edb5c3fb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -447,14 +447,6 @@ public class UserSwitcherController implements Dumpable { mResumeUserOnGuestLogout = resume; } - public void logoutCurrentUser() { - int currentUser = mUserTracker.getUserId(); - if (currentUser != UserHandle.USER_SYSTEM) { - pauseRefreshUsers(); - ActivityManager.logoutCurrentUser(); - } - } - /** * Returns whether the current user is a system user. */ diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java index 5aaf7f680d5c..1bf5f076ac2f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java @@ -303,6 +303,7 @@ public interface SettingsProxy { default boolean putInt(String name, int value) { return putIntForUser(name, value, getUserId()); } + /** See {@link #putInt(String, int)}. */ default boolean putIntForUser(String name, int value, int userHandle) { return putStringForUser(name, Integer.toString(value), userHandle); @@ -310,6 +311,76 @@ public interface SettingsProxy { /** * Convenience function for retrieving a single secure settings value + * as a boolean. Note that internally setting values are always + * stored as strings; this function converts the string to a boolean + * for you. The default value will be returned if the setting is + * not defined or not a boolean. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid boolean. + */ + default boolean getBool(String name, boolean def) { + return getBoolForUser(name, def, getUserId()); + } + + /** See {@link #getBool(String, boolean)}. */ + default boolean getBoolForUser(String name, boolean def, int userHandle) { + return getIntForUser(name, def ? 1 : 0, userHandle) != 0; + } + + /** + * Convenience function for retrieving a single secure settings value + * as a boolean. Note that internally setting values are always + * stored as strings; this function converts the string to a boolean + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link Settings.SettingNotFoundException}. + * + * @param name The name of the setting to retrieve. + * + * @throws Settings.SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not a boolean. + * + * @return The setting's current value. + */ + default boolean getBool(String name) throws Settings.SettingNotFoundException { + return getBoolForUser(name, getUserId()); + } + + /** See {@link #getBool(String)}. */ + default boolean getBoolForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + return getIntForUser(name, userHandle) != 0; + } + + /** + * Convenience function for updating a single settings value as a + * boolean. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + default boolean putBool(String name, boolean value) { + return putBoolForUser(name, value, getUserId()); + } + + /** See {@link #putBool(String, boolean)}. */ + default boolean putBoolForUser(String name, boolean value, int userHandle) { + return putIntForUser(name, value ? 1 : 0, userHandle); + } + + /** + * Convenience function for retrieving a single secure settings value * as a {@code long}. Note that internally setting values are always * stored as strings; this function converts the string to a {@code long} * for you. The default value will be returned if the setting is diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index a7197cca530c..1518ea160016 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -84,4 +84,10 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { mMessageAreaController.setMessage(""); verify(mKeyguardMessageArea).setMessage(""); } + + @Test + public void testSetBouncerVisible() { + mMessageAreaController.setBouncerShowing(true); + verify(mKeyguardMessageArea).setBouncerShowing(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java index 31fb25a7a89c..013c298d69d7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaTest.java @@ -40,7 +40,7 @@ public class KeyguardMessageAreaTest extends SysuiTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mKeyguardMessageArea = new KeyguardMessageArea(mContext, null); - mKeyguardMessageArea.setBouncerVisible(true); + mKeyguardMessageArea.setBouncerShowing(true); } @Test @@ -53,7 +53,7 @@ public class KeyguardMessageAreaTest extends SysuiTestCase { @Test public void testHiddenWhenBouncerHidden() { - mKeyguardMessageArea.setBouncerVisible(false); + mKeyguardMessageArea.setBouncerShowing(false); mKeyguardMessageArea.setVisibility(View.INVISIBLE); mKeyguardMessageArea.setMessage("oobleck"); assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index ec92adb0f48c..ac78626ff126 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -33,7 +33,6 @@ import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -61,9 +60,11 @@ import android.util.Size; import android.view.Display; import android.view.DisplayCutout; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowMetrics; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -102,7 +103,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private SecureSettings mSecureSettings; private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private FakeThreadFactory mThreadFactory; - private ArrayList<DecorProvider> mDecorProviders; + private ArrayList<DecorProvider> mPrivacyDecorProviders; @Mock private Display mDisplay; @Mock @@ -197,17 +198,43 @@ public class ScreenDecorationsTest extends SysuiTestCase { reset(mTunerService); } + @NonNull + private int[] getRoundCornerIdsFromOverlayId(@DisplayCutout.BoundsPosition int overlayId) { + switch (overlayId) { + case BOUNDS_POSITION_LEFT: + return new int[] { + R.id.rounded_corner_top_left, + R.id.rounded_corner_top_left }; + case BOUNDS_POSITION_TOP: + return new int[] { + R.id.rounded_corner_top_left, + R.id.rounded_corner_top_right }; + case BOUNDS_POSITION_RIGHT: + return new int[] { + R.id.rounded_corner_top_right, + R.id.rounded_corner_bottom_right }; + case BOUNDS_POSITION_BOTTOM: + return new int[] { + R.id.rounded_corner_bottom_left, + R.id.rounded_corner_bottom_right }; + default: + throw new IllegalArgumentException("unknown overlayId: " + overlayId); + } + } - private void verifyRoundedCornerViewsVisibility( + private void verifyRoundedCornerViewsExist( @DisplayCutout.BoundsPosition final int overlayId, - @View.Visibility final int visibility) { + @View.Visibility final boolean isExist) { final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView(); - final View left = overlay.findViewById(R.id.left); - final View right = overlay.findViewById(R.id.right); - assertNotNull(left); - assertNotNull(right); - assertThat(left.getVisibility()).isEqualTo(visibility); - assertThat(right.getVisibility()).isEqualTo(visibility); + for (int id: getRoundCornerIdsFromOverlayId(overlayId)) { + final View view = overlay.findViewById(id); + if (isExist) { + assertNotNull(view); + assertThat(view.getVisibility()).isEqualTo(View.VISIBLE); + } else { + assertNull(view); + } + } } @Nullable @@ -351,8 +378,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(false, true, false, true); // Rounded corner views shall not exist - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false); // Privacy dots shall exist but invisible verifyDotViewsVisibility(View.INVISIBLE); @@ -380,8 +407,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(false, true, false, true); // Rounded corner views shall exist - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true); // Privacy dots shall not exist verifyDotViewsNullable(true); @@ -408,8 +435,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(false, true, false, true); // Rounded corner views shall exist - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true); // Privacy dots shall exist but invisible verifyDotViewsVisibility(View.INVISIBLE); @@ -449,21 +476,26 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.start(); View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView() - .findViewById(R.id.left); + .findViewById(R.id.rounded_corner_top_left); View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView() - .findViewById(R.id.right); - verify(mScreenDecorations, atLeastOnce()) - .setSize(leftRoundedCorner, new Size(testTopRadius, testTopRadius)); - verify(mScreenDecorations, atLeastOnce()) - .setSize(rightRoundedCorner, new Size(testTopRadius, testTopRadius)); + .findViewById(R.id.rounded_corner_top_right); + ViewGroup.LayoutParams leftParams = leftRoundedCorner.getLayoutParams(); + ViewGroup.LayoutParams rightParams = rightRoundedCorner.getLayoutParams(); + assertEquals(leftParams.width, testTopRadius); + assertEquals(leftParams.height, testTopRadius); + assertEquals(rightParams.width, testTopRadius); + assertEquals(rightParams.height, testTopRadius); + leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView() - .findViewById(R.id.left); + .findViewById(R.id.rounded_corner_bottom_left); rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView() - .findViewById(R.id.right); - verify(mScreenDecorations, atLeastOnce()) - .setSize(leftRoundedCorner, new Size(testBottomRadius, testBottomRadius)); - verify(mScreenDecorations, atLeastOnce()) - .setSize(rightRoundedCorner, new Size(testBottomRadius, testBottomRadius)); + .findViewById(R.id.rounded_corner_bottom_right); + leftParams = leftRoundedCorner.getLayoutParams(); + rightParams = rightRoundedCorner.getLayoutParams(); + assertEquals(leftParams.width, testBottomRadius); + assertEquals(leftParams.height, testBottomRadius); + assertEquals(rightParams.width, testBottomRadius); + assertEquals(rightParams.height, testBottomRadius); } @Test @@ -479,31 +511,27 @@ public class ScreenDecorationsTest extends SysuiTestCase { .when(mScreenDecorations).getCutout(); mScreenDecorations.start(); - final Size topRadius = new Size(testTopRadius, testTopRadius); - final Size bottomRadius = new Size(testBottomRadius, testBottomRadius); - View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() - .findViewById(R.id.left); - boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left); - verify(mScreenDecorations, atLeastOnce()) - .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius); - - View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() - .findViewById(R.id.right); - isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right); - verify(mScreenDecorations, atLeastOnce()) - .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius); - - leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView() - .findViewById(R.id.left); - isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left); - verify(mScreenDecorations, atLeastOnce()) - .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius); - - rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView() - .findViewById(R.id.right); - isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right); - verify(mScreenDecorations, atLeastOnce()) - .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius); + View topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() + .findViewById(R.id.rounded_corner_top_left); + View bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() + .findViewById(R.id.rounded_corner_bottom_left); + ViewGroup.LayoutParams topParams = topRoundedCorner.getLayoutParams(); + ViewGroup.LayoutParams bottomParams = bottomRoundedCorner.getLayoutParams(); + assertEquals(topParams.width, testTopRadius); + assertEquals(topParams.height, testTopRadius); + assertEquals(bottomParams.width, testBottomRadius); + assertEquals(bottomParams.height, testBottomRadius); + + topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView() + .findViewById(R.id.rounded_corner_top_right); + bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView() + .findViewById(R.id.rounded_corner_bottom_right); + topParams = topRoundedCorner.getLayoutParams(); + bottomParams = bottomRoundedCorner.getLayoutParams(); + assertEquals(topParams.width, testTopRadius); + assertEquals(topParams.height, testTopRadius); + assertEquals(bottomParams.width, testBottomRadius); + assertEquals(bottomParams.height, testBottomRadius); } @Test @@ -523,8 +551,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(false, true, false, true); // Rounded corner views shall exist - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true); // Privacy dots shall not exist verifyDotViewsNullable(true); @@ -557,8 +585,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(false, true, false, true); // Rounded corner views shall exist - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true); // Privacy dots shall exist but invisible verifyDotViewsVisibility(View.INVISIBLE); @@ -617,10 +645,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { // Top rounded corner views shall exist because of cutout // but be gone because of no rounded corner - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false); // Bottom rounded corner views shall exist because of privacy dot // but be gone because of no rounded corner - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false); // Privacy dots shall exist but invisible verifyDotViewsVisibility(View.INVISIBLE); @@ -648,7 +676,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { // Left rounded corner views shall exist because of cutout // but be gone because of no rounded corner - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_LEFT, View.GONE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_LEFT, false); // Top privacy dots shall not exist because of no privacy verifyDotViewsNullable(true); @@ -700,8 +728,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(false, true, false, true); // Rounded corner views shall exist - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true); // Top privacy dots shall not exist because of no privacy dot verifyDotViewsNullable(true); @@ -728,8 +756,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(false, true, false, true); // Rounded corner views shall exist - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true); // Top privacy dots shall exist but invisible verifyDotViewsVisibility(View.INVISIBLE); @@ -859,7 +887,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { verifyOverlaysExistAndAdded(true, false, true, false); // Verify each privacy dot id appears only once - mDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> { + mPrivacyDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> { int findCount = 0; for (OverlayWindow overlay: mScreenDecorations.mOverlays) { if (overlay == null) { @@ -913,8 +941,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { // Both top and bottom windows should be added because of privacy dot, // but their visibility shall be gone because of no rounding. verifyOverlaysExistAndAdded(false, true, false, true); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false); when(mContext.getResources().getBoolean( com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout)) @@ -925,8 +953,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { // Both top and bottom windows should be added because of privacy dot, // but their visibility shall be gone because of no rounding. verifyOverlaysExistAndAdded(false, true, false, true); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE); - verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false); + verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false); } @Test @@ -1174,14 +1202,14 @@ public class ScreenDecorationsTest extends SysuiTestCase { mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout); - mDecorProviders = new ArrayList<>(); + mPrivacyDecorProviders = new ArrayList<>(); if (privacyDot) { - mDecorProviders.add(mPrivacyDotTopLeftDecorProvider); - mDecorProviders.add(mPrivacyDotTopRightDecorProvider); - mDecorProviders.add(mPrivacyDotBottomLeftDecorProvider); - mDecorProviders.add(mPrivacyDotBottomRightDecorProvider); + mPrivacyDecorProviders.add(mPrivacyDotTopLeftDecorProvider); + mPrivacyDecorProviders.add(mPrivacyDotTopRightDecorProvider); + mPrivacyDecorProviders.add(mPrivacyDotBottomLeftDecorProvider); + mPrivacyDecorProviders.add(mPrivacyDotBottomRightDecorProvider); } - when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mDecorProviders); + when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mPrivacyDecorProviders); when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt index 214fd4d28398..8eb0918beedf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt @@ -19,7 +19,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -class ViewBoundAnimatorTest : SysuiTestCase() { +class ViewHierarchyAnimatorTest : SysuiTestCase() { companion object { private const val TEST_DURATION = 1000L private val TEST_INTERPOLATOR = Interpolators.LINEAR @@ -34,14 +34,14 @@ class ViewBoundAnimatorTest : SysuiTestCase() { @After fun tearDown() { - ViewBoundAnimator.stopAnimating(rootView) + ViewHierarchyAnimator.stopAnimating(rootView) } @Test fun respectsAnimationParameters() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animate( + ViewHierarchyAnimator.animate( rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION ) rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */) @@ -56,7 +56,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun animatesFromStartToEnd() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animate(rootView) + ViewHierarchyAnimator.animate(rootView) // Change all bounds. rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) @@ -73,7 +73,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun animatesSuccessiveLayoutChanges() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animate(rootView) + ViewHierarchyAnimator.animate(rootView) // Change all bounds. rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) @@ -103,7 +103,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun animatesFromPreviousAnimationProgress() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR) + ViewHierarchyAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR) // Change all bounds. rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */) @@ -131,7 +131,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { firstChild.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */) secondChild.layout(100 /* l */, 0 /* t */, 150 /* r */, 100 /* b */) - ViewBoundAnimator.animate(rootView) + ViewHierarchyAnimator.animate(rootView) // Change all bounds. rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */) firstChild.layout(10 /* l */, 20 /* t */, 150 /* r */, 120 /* b */) @@ -154,7 +154,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun doesNotAnimateInvisibleViews() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animate(rootView) + ViewHierarchyAnimator.animate(rootView) // GONE. rootView.visibility = View.GONE rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */) @@ -171,7 +171,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun doesNotAnimateUnchangingBounds() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animate(rootView) + ViewHierarchyAnimator.animate(rootView) // No bounds are changed. rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) @@ -191,9 +191,9 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun doesNotAnimateExcludedBounds() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animate( + ViewHierarchyAnimator.animate( rootView, - bounds = setOf(ViewBoundAnimator.Bound.LEFT, ViewBoundAnimator.Bound.TOP), + bounds = setOf(ViewHierarchyAnimator.Bound.LEFT, ViewHierarchyAnimator.Bound.TOP), interpolator = TEST_INTERPOLATOR ) // Change all bounds. @@ -211,7 +211,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun stopsAnimatingAfterSingleLayout() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animateNextUpdate(rootView) + ViewHierarchyAnimator.animateNextUpdate(rootView) // Change all bounds. rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) @@ -231,7 +231,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { fun stopsAnimatingWhenInstructed() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) - ViewBoundAnimator.animate(rootView) + ViewHierarchyAnimator.animate(rootView) // Change all bounds. rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) @@ -240,7 +240,7 @@ class ViewBoundAnimatorTest : SysuiTestCase() { assertNull(rootView.getTag(R.id.tag_animator)) checkBounds(rootView, l = 0, t = 15, r = 70, b = 80) - ViewBoundAnimator.stopAnimating(rootView) + ViewHierarchyAnimator.stopAnimating(rootView) // Change all bounds again. rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */) diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt index ca74df0a23c5..5182210b9567 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt @@ -19,25 +19,19 @@ package com.android.systemui.decor import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.DisplayCutout -import android.view.LayoutInflater import android.view.Surface import android.view.View -import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.util.mockito.eq import org.junit.Assert import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.anyInt +import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) @@ -45,62 +39,88 @@ import org.mockito.Mockito.`when` as whenever class OverlayWindowTest : SysuiTestCase() { companion object { - private val TEST_DECOR_VIEW_ID = R.id.privacy_dot_bottom_right_container - private val TEST_DECOR_LAYOUT_ID = R.layout.privacy_dot_bottom_right + private val TEST_DECOR_VIEW_ID_1 = R.id.privacy_dot_top_left_container + private val TEST_DECOR_VIEW_ID_2 = R.id.privacy_dot_bottom_right_container } private lateinit var overlay: OverlayWindow - - @Mock private lateinit var layoutInflater: LayoutInflater - @Mock private lateinit var decorProvider: DecorProvider + private lateinit var decorProvider1: DecorProvider + private lateinit var decorProvider2: DecorProvider @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - layoutInflater = spy(LayoutInflater.from(mContext)) - - overlay = OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT) - - whenever(decorProvider.viewId).thenReturn(TEST_DECOR_VIEW_ID) - whenever(decorProvider.inflateView( - eq(layoutInflater), - eq(overlay.rootView), - anyInt()) - ).then { - val layoutInflater = it.getArgument<LayoutInflater>(0) - val parent = it.getArgument<ViewGroup>(1) - layoutInflater.inflate(TEST_DECOR_LAYOUT_ID, parent) - return@then parent.getChildAt(parent.childCount - 1) - } - } + decorProvider1 = spy(PrivacyDotCornerDecorProviderImpl( + viewId = TEST_DECOR_VIEW_ID_1, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, + layoutId = R.layout.privacy_dot_top_left)) + decorProvider2 = spy(PrivacyDotCornerDecorProviderImpl( + viewId = TEST_DECOR_VIEW_ID_2, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, + layoutId = R.layout.privacy_dot_bottom_right)) - @Test - fun testAnyBoundsPositionShallNoExceptionForConstructor() { - OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_LEFT) - OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_TOP) - OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT) - OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_BOTTOM) + overlay = OverlayWindow(mContext) } @Test fun testAddProvider() { @Surface.Rotation val rotation = Surface.ROTATION_270 - overlay.addDecorProvider(decorProvider, rotation) - verify(decorProvider, Mockito.times(1)).inflateView( - eq(layoutInflater), eq(overlay.rootView), eq(rotation)) - val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID) - Assert.assertNotNull(viewFoundFromRootView) - Assert.assertEquals(viewFoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID)) + overlay.addDecorProvider(decorProvider1, rotation) + overlay.addDecorProvider(decorProvider2, rotation) + + verify(decorProvider1, times(1)).inflateView( + mContext, overlay.rootView, rotation) + verify(decorProvider2, times(1)).inflateView( + mContext, overlay.rootView, rotation) + + val view1FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1) + Assert.assertNotNull(view1FoundFromRootView) + Assert.assertEquals(view1FoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID_1)) + val view2FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_2) + Assert.assertNotNull(view2FoundFromRootView) + Assert.assertEquals(view2FoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID_2)) } @Test fun testRemoveView() { - @Surface.Rotation val rotation = Surface.ROTATION_270 - overlay.addDecorProvider(decorProvider, rotation) - overlay.removeView(TEST_DECOR_VIEW_ID) - val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID) + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270) + overlay.removeView(TEST_DECOR_VIEW_ID_1) + + val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1) Assert.assertNull(viewFoundFromRootView) - Assert.assertNull(overlay.getView(TEST_DECOR_LAYOUT_ID)) + Assert.assertNull(overlay.getView(TEST_DECOR_VIEW_ID_1)) + } + + @Test + fun testOnReloadResAndMeasureWithoutIds() { + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0) + + overlay.onReloadResAndMeasure( + reloadToken = 1, + rotation = Surface.ROTATION_90, + displayUniqueId = null) + verify(decorProvider1, times(1)).onReloadResAndMeasure( + overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null) + verify(decorProvider2, times(1)).onReloadResAndMeasure( + overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null) + } + + @Test + fun testOnReloadResAndMeasureWithIds() { + overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0) + overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0) + + overlay.onReloadResAndMeasure( + filterIds = arrayOf(TEST_DECOR_VIEW_ID_2), + reloadToken = 1, + rotation = Surface.ROTATION_90, + displayUniqueId = null) + verify(decorProvider1, never()).onReloadResAndMeasure( + overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null) + verify(decorProvider2, times(1)).onReloadResAndMeasure( + overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt index bac08176d2eb..171b76748d26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.decor import android.content.res.Resources import android.testing.AndroidTestingRunner -import android.testing.TestableLooper.RunWithLooper import android.view.DisplayCutout import androidx.test.filters.SmallTest import com.android.systemui.R @@ -32,7 +31,6 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) -@RunWithLooper(setAsMainLooper = true) @SmallTest class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() { private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt new file mode 100644 index 000000000000..621bcf69bb03 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.testing.AndroidTestingRunner +import android.util.Size +import android.view.DisplayCutout +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.spy + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class RoundedCornerDecorProviderFactoryTest : SysuiTestCase() { + + @Mock private lateinit var roundedCornerResDelegate: RoundedCornerResDelegate + private lateinit var roundedCornerDecorProviderFactory: RoundedCornerDecorProviderFactory + + @Before + fun setUp() { + roundedCornerResDelegate = spy(RoundedCornerResDelegate(mContext.resources, null)) + } + + @Test + fun testNoRoundedCorners() { + Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).topRoundedSize + Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).bottomRoundedSize + Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius + + roundedCornerDecorProviderFactory = + RoundedCornerDecorProviderFactory(roundedCornerResDelegate) + + Assert.assertEquals(false, roundedCornerDecorProviderFactory.hasProviders) + Assert.assertEquals(0, roundedCornerDecorProviderFactory.providers.size) + } + + @Test + fun testHasRoundedCornersIfTopWidthLargerThan0() { + Mockito.doReturn(Size(1, 0)).`when`(roundedCornerResDelegate).topRoundedSize + Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).bottomRoundedSize + Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius + + roundedCornerDecorProviderFactory = + RoundedCornerDecorProviderFactory(roundedCornerResDelegate) + + Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders) + roundedCornerDecorProviderFactory.providers.let { providers -> + Assert.assertEquals(2, providers.size) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_top_left) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_top_right) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT)) + }) + } + } + + @Test + fun testHasRoundedCornersIfBottomWidthLargerThan0() { + Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).topRoundedSize + Mockito.doReturn(Size(1, 1)).`when`(roundedCornerResDelegate).bottomRoundedSize + Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius + + roundedCornerDecorProviderFactory = + RoundedCornerDecorProviderFactory(roundedCornerResDelegate) + + Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders) + roundedCornerDecorProviderFactory.providers.let { providers -> + Assert.assertEquals(2, providers.size) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_bottom_left) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_bottom_right) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT)) + }) + } + } + + @Test + fun test4CornerDecorProvidersInfo() { + Mockito.doReturn(Size(10, 10)).`when`(roundedCornerResDelegate).topRoundedSize + Mockito.doReturn(Size(10, 10)).`when`(roundedCornerResDelegate).bottomRoundedSize + Mockito.doReturn(true).`when`(roundedCornerResDelegate).isMultipleRadius + + roundedCornerDecorProviderFactory = + RoundedCornerDecorProviderFactory(roundedCornerResDelegate) + + Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders) + roundedCornerDecorProviderFactory.providers.let { providers -> + Assert.assertEquals(4, providers.size) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_top_left) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_top_right) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_bottom_left) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.rounded_corner_bottom_right) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT)) + }) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt index b536bfdb944e..e89ed309a1b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt @@ -45,7 +45,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { } @Test - fun testReloadAllAndDefaultRadius() { + fun testUpdateDisplayUniqueId() { mContext.orCreateTestableResources.addOverrides( mockTypeArray = mockTypedArray, radius = 3, @@ -65,7 +65,34 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { radiusTop = 6, radiusBottom = 0) - roundedCornerResDelegate.reloadAll("test") + roundedCornerResDelegate.updateDisplayUniqueId("test", null) + + assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize) + assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize) + } + + @Test + fun testNotUpdateDisplayUniqueIdButChangeRefreshToken() { + mContext.orCreateTestableResources.addOverrides( + mockTypeArray = mockTypedArray, + radius = 3, + radiusTop = 0, + radiusBottom = 4, + multipleRadius = false) + + roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) + + assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize) + assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) + assertEquals(false, roundedCornerResDelegate.isMultipleRadius) + + mContext.orCreateTestableResources.addOverrides( + mockTypeArray = mockTypedArray, + radius = 5, + radiusTop = 6, + radiusBottom = 0) + + roundedCornerResDelegate.updateDisplayUniqueId(null, 1) assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize) @@ -75,18 +102,24 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { fun testUpdateTuningSizeFactor() { mContext.orCreateTestableResources.addOverrides( mockTypeArray = mockTypedArray, + radius = 1, radiusTop = 0, - radiusBottom = 0, + radiusBottom = 2, multipleRadius = false) roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) val factor = 5 - roundedCornerResDelegate.updateTuningSizeFactor(factor) + roundedCornerResDelegate.updateTuningSizeFactor(factor, 1) val length = (factor * mContext.resources.displayMetrics.density).toInt() assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(length, length), roundedCornerResDelegate.bottomRoundedSize) + + roundedCornerResDelegate.updateTuningSizeFactor(null, 2) + + assertEquals(Size(1, 1), roundedCornerResDelegate.topRoundedSize) + assertEquals(Size(2, 2), roundedCornerResDelegate.bottomRoundedSize) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 3657192daede..f567b55b7caa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -38,6 +38,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import androidx.test.filters.SmallTest; +import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.complication.DreamPreviewComplication; @@ -104,6 +105,9 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Mock ViewGroup mDreamOverlayContainerViewParent; + @Mock + UiEventLogger mUiEventLogger; + DreamOverlayService mService; @Before @@ -129,7 +133,22 @@ public class DreamOverlayServiceTest extends SysuiTestCase { mDreamOverlayComponentFactory, mStateController, mKeyguardUpdateMonitor, - mPreviewComplication); + mPreviewComplication, + mUiEventLogger); + } + + @Test + public void testOnStartMetricsLogged() throws Exception { + final IBinder proxy = mService.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback); + mMainExecutor.runAllReady(); + + verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); + verify(mUiEventLogger).log( + DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java index 6e01541c75e0..ce92d5e30455 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java @@ -39,8 +39,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.wm.shell.animation.FlingAnimationUtils; @@ -112,8 +112,10 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { TOUCH_REGION); when(mCentralSurfaces.getDisplayHeight()).thenReturn((float) SCREEN_HEIGHT_PX); + when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker); + when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE); } /** @@ -154,7 +156,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { * Makes sure expansion amount is proportional to scroll. */ @Test - public void testExpansionAmount() { + public void testExpansionAmount_whenBouncerHidden_setsCorrectValue() { mTouchHandler.onSessionStart(mTouchSession); ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); @@ -166,9 +168,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, SCREEN_HEIGHT_PX, 0); final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, SCREEN_HEIGHT_PX - distanceY, 0); + 0, SCREEN_HEIGHT_PX - distanceY, 0); - assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY)) + assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0, distanceY)) .isTrue(); // Ensure only called once @@ -180,6 +182,38 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { .onPanelExpansionChanged(eq(1 - scrollAmount), eq(false), eq(true)); } + /** + * Makes sure expansion amount is proportional to scroll. + */ + @Test + public void testExpansionAmount_whenBouncerShown_setsCorrectValue() { + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + + mTouchHandler.onSessionStart(mTouchSession); + ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = + ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); + verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); + + final float scrollAmount = .3f; + final float distanceY = SCREEN_HEIGHT_PX * scrollAmount; + + final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, + 0, SCREEN_HEIGHT_PX, 0); + final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, + 0, SCREEN_HEIGHT_PX - distanceY, 0); + + assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0, distanceY)) + .isTrue(); + + // Ensure only called once + verify(mStatusBarKeyguardViewManager) + .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean()); + + // Ensure correct expansion passed in. + verify(mStatusBarKeyguardViewManager) + .onPanelExpansionChanged(eq(scrollAmount), eq(false), eq(true)); + } + private void swipeToPosition(float position, float velocityY) { mTouchHandler.onSessionStart(mTouchSession); ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = @@ -196,9 +230,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, SCREEN_HEIGHT_PX, 0); final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, - 0, SCREEN_HEIGHT_PX - distanceY, 0); + 0, SCREEN_HEIGHT_PX - distanceY, 0); - assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY)) + assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0, distanceY)) .isTrue(); final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, @@ -212,14 +246,18 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { * down. */ @Test - public void testCollapseOnThreshold() { + public void testSwipeBelowThreshold_collapsesBouncer() { final float swipeUpPercentage = .3f; - swipeToPosition(swipeUpPercentage, -1); - - verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage), - eq(KeyguardBouncer.EXPANSION_VISIBLE)); - verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), anyFloat(), anyFloat(), - anyFloat(), anyFloat()); + final float expansion = 1 - swipeUpPercentage; + // The upward velocity is ignored. + final float velocityY = -1; + swipeToPosition(swipeUpPercentage, velocityY); + + verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_HIDDEN)); + verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), + eq(SCREEN_HEIGHT_PX * expansion), + eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_HIDDEN), + eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); verify(mValueAnimator).start(); } @@ -227,14 +265,59 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { * Tests that ending a swipe above the set expansion threshold will continue the expansion. */ @Test - public void testExpandOnThreshold() { + public void testSwipeAboveThreshold_expandsBouncer() { final float swipeUpPercentage = .7f; - swipeToPosition(swipeUpPercentage, 1); + final float expansion = 1 - swipeUpPercentage; + // The downward velocity is ignored. + final float velocityY = 1; + swipeToPosition(swipeUpPercentage, velocityY); + + verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_VISIBLE)); + verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion), + eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE), + eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); + verify(mValueAnimator).start(); + } - verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage), - eq(KeyguardBouncer.EXPANSION_HIDDEN)); - verify(mFlingAnimationUtils).apply(eq(mValueAnimator), anyFloat(), anyFloat(), - anyFloat(), anyFloat()); + /** + * Tests that swiping down with a speed above the set threshold leads to bouncer collapsing + * down. + */ + @Test + public void testSwipeDownVelocityAboveMin_collapsesBouncer() { + when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0); + + // The swipe amount above the set expansion threshold is ignored. + final float swipeUpPercentage = .7f; + final float expansion = 1 - swipeUpPercentage; + final float velocityY = 1; + swipeToPosition(swipeUpPercentage, velocityY); + + verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_HIDDEN)); + verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), + eq(SCREEN_HEIGHT_PX * expansion), + eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_HIDDEN), + eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); + verify(mValueAnimator).start(); + } + + /** + * Tests that swiping up with a speed above the set threshold will continue the expansion. + */ + @Test + public void testSwipeUpVelocityAboveMin_expandsBouncer() { + when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0); + + // The swipe amount below the set expansion threshold is ignored. + final float swipeUpPercentage = .3f; + final float expansion = 1 - swipeUpPercentage; + final float velocityY = -1; + swipeToPosition(swipeUpPercentage, velocityY); + + verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_VISIBLE)); + verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion), + eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE), + eq(velocityY), eq((float) SCREEN_HEIGHT_PX)); verify(mValueAnimator).start(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt index 1484c9d11ba6..fcfef4a44128 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt @@ -52,8 +52,6 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private lateinit var statusBarStateController: SysuiStatusBarStateController @Mock private lateinit var configurationController: ConfigurationController - @Mock - private lateinit var mediaFlags: MediaFlags @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager @@ -73,15 +71,13 @@ class KeyguardMediaControllerTest : SysuiTestCase() { .thenReturn(true) whenever(mediaHost.hostView).thenReturn(hostView) hostView.layoutParams = FrameLayout.LayoutParams(100, 100) - whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false) keyguardMediaController = KeyguardMediaController( mediaHost, bypassController, statusBarStateController, notificationLockscreenUserManager, context, - configurationController, - mediaFlags + configurationController ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) keyguardMediaController.useSplitShade = false @@ -157,22 +153,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test - fun testNotificationLayout_collapsedPlayer() { - verify(mediaHost).expansion = MediaHostState.COLLAPSED - } - - @Test - fun testSessionLayout_expandedPlayer() { - whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true) - keyguardMediaController = KeyguardMediaController( - mediaHost, - bypassController, - statusBarStateController, - notificationLockscreenUserManager, - context, - configurationController, - mediaFlags - ) + fun testMediaHost_expandedPlayer() { verify(mediaHost).expansion = MediaHostState.EXPANDED } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 90eff1ae9804..1c7e3a4e90cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -89,8 +89,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var broadcastSender: BroadcastSender - @Mock private lateinit var holder: PlayerViewHolder - @Mock private lateinit var sessionHolder: PlayerSessionViewHolder + @Mock private lateinit var viewHolder: MediaViewHolder @Mock private lateinit var view: TransitionLayout @Mock private lateinit var seekBarViewModel: SeekBarViewModel @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> @@ -123,8 +122,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var actionPrev: ImageButton @Mock private lateinit var longPressText: TextView @Mock private lateinit var handler: Handler - private lateinit var settings: View - private lateinit var settingsText: TextView + private lateinit var settings: ImageButton private lateinit var cancel: View private lateinit var cancelText: TextView private lateinit var dismiss: FrameLayout @@ -162,8 +160,7 @@ public class MediaControlPanelTest : SysuiTestCase() { seekBar = SeekBar(context) elapsedTimeView = TextView(context) totalTimeView = TextView(context) - settings = View(context) - settingsText = TextView(context) + settings = ImageButton(context) cancel = View(context) cancelText = TextView(context) dismiss = FrameLayout(context) @@ -179,8 +176,7 @@ public class MediaControlPanelTest : SysuiTestCase() { actionPrev = ImageButton(context).also { it.setId(R.id.actionPrev) } actionNext = ImageButton(context).also { it.setId(R.id.actionNext) } - initPlayerHolderMocks() - initSessionHolderMocks() + initMediaViewHolderMocks() // Create media session val metadataBuilder = MediaMetadata.Builder().apply { @@ -217,9 +213,9 @@ public class MediaControlPanelTest : SysuiTestCase() { } /** - * Initialize elements common to both view holders + * Initialize elements in media view holder */ - private fun initMediaViewHolderMocks(viewHolder: MediaViewHolder) { + private fun initMediaViewHolderMocks() { whenever(viewHolder.player).thenReturn(view) whenever(viewHolder.appIcon).thenReturn(appIcon) whenever(viewHolder.albumView).thenReturn(albumView) @@ -233,6 +229,12 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.seekBar).thenReturn(seekBar) // Action buttons + whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause) + whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause) + whenever(viewHolder.actionNext).thenReturn(actionNext) + whenever(viewHolder.getAction(R.id.actionNext)).thenReturn(actionNext) + whenever(viewHolder.actionPrev).thenReturn(actionPrev) + whenever(viewHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev) whenever(viewHolder.action0).thenReturn(action0) whenever(viewHolder.getAction(R.id.action0)).thenReturn(action0) whenever(viewHolder.action1).thenReturn(action1) @@ -248,34 +250,12 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.longPressText).thenReturn(longPressText) whenever(longPressText.handler).thenReturn(handler) whenever(viewHolder.settings).thenReturn(settings) - whenever(viewHolder.settingsText).thenReturn(settingsText) whenever(viewHolder.cancel).thenReturn(cancel) whenever(viewHolder.cancelText).thenReturn(cancelText) whenever(viewHolder.dismiss).thenReturn(dismiss) whenever(viewHolder.dismissText).thenReturn(dismissText) } - /** Mock view holder for the notification player */ - private fun initPlayerHolderMocks() { - initMediaViewHolderMocks(holder) - - whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView) - whenever(holder.totalTimeView).thenReturn(totalTimeView) - } - - /** Mock view holder for session player */ - private fun initSessionHolderMocks() { - initMediaViewHolderMocks(sessionHolder) - - // Semantic action buttons - whenever(sessionHolder.actionPlayPause).thenReturn(actionPlayPause) - whenever(sessionHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause) - whenever(sessionHolder.actionNext).thenReturn(actionNext) - whenever(sessionHolder.getAction(R.id.actionNext)).thenReturn(actionNext) - whenever(sessionHolder.actionPrev).thenReturn(actionPrev) - whenever(sessionHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev) - } - @After fun tearDown() { session.release() @@ -290,41 +270,7 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test - fun bindSemanticActionsOldLayout() { - val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - playOrPause = MediaAction(icon, Runnable {}, "play"), - nextOrCustom = MediaAction(icon, Runnable {}, "next"), - custom0 = MediaAction(icon, null, "custom 0"), - custom1 = MediaAction(icon, null, "custom 1") - ) - val state = mediaData.copy(semanticActions = semanticActions) - - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) - player.bindPlayer(state, PACKAGE) - - verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE) - assertThat(action0.contentDescription).isEqualTo("custom 0") - assertThat(action0.isEnabled()).isFalse() - - verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE) - assertThat(action1.isEnabled()).isFalse() - - verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE) - assertThat(action2.isEnabled()).isTrue() - assertThat(action2.contentDescription).isEqualTo("play") - - verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE) - assertThat(action3.isEnabled()).isTrue() - assertThat(action3.contentDescription).isEqualTo("next") - - verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE) - assertThat(action4.contentDescription).isEqualTo("custom 1") - assertThat(action4.isEnabled()).isFalse() - } - - @Test - fun bindSemanticActionsNewLayout() { + fun bindSemanticActions() { val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val semanticActions = MediaButton( playOrPause = MediaAction(icon, Runnable {}, "play"), @@ -334,7 +280,7 @@ public class MediaControlPanelTest : SysuiTestCase() { ) val state = mediaData.copy(semanticActions = semanticActions) - player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION) + player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) assertThat(actionPrev.isEnabled()).isFalse() @@ -370,7 +316,7 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test - fun bindNotificationActionsNewLayout() { + fun bindNotificationActions() { val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val actions = listOf( MediaAction(icon, Runnable {}, "previous"), @@ -383,7 +329,7 @@ public class MediaControlPanelTest : SysuiTestCase() { actionsToShowInCompact = listOf(1, 2), semanticActions = null) - player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION) + player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) // Verify semantic actions are hidden @@ -420,7 +366,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindText() { - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) player.bindPlayer(mediaData, PACKAGE) assertThat(titleText.getText()).isEqualTo(TITLE) assertThat(artistText.getText()).isEqualTo(ARTIST) @@ -428,7 +374,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindDevice() { - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) player.bindPlayer(mediaData, PACKAGE) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME) @@ -438,7 +384,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindDisabledDevice() { seamless.id = 1 - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) val state = mediaData.copy(device = disabledDevice) player.bindPlayer(state, PACKAGE) assertThat(seamless.isEnabled()).isFalse() @@ -449,7 +395,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindNullDevice() { val fallbackString = context.getResources().getString(R.string.media_seamless_other_device) - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) val state = mediaData.copy(device = null) player.bindPlayer(state, PACKAGE) assertThat(seamless.isEnabled()).isTrue() @@ -459,7 +405,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindDeviceResumptionPlayer() { - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) val state = mediaData.copy(resumption = true) player.bindPlayer(state, PACKAGE) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) @@ -468,32 +414,32 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun longClick_gutsClosed() { - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) whenever(mediaViewController.isGutsVisible).thenReturn(false) val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(holder.player).setOnLongClickListener(captor.capture()) + verify(viewHolder.player).setOnLongClickListener(captor.capture()) - captor.value.onLongClick(holder.player) + captor.value.onLongClick(viewHolder.player) verify(mediaViewController).openGuts() } @Test fun longClick_gutsOpen() { - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) whenever(mediaViewController.isGutsVisible).thenReturn(true) val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) - verify(holder.player).setOnLongClickListener(captor.capture()) + verify(viewHolder.player).setOnLongClickListener(captor.capture()) - captor.value.onLongClick(holder.player) + captor.value.onLongClick(viewHolder.player) verify(mediaViewController, never()).openGuts() verify(mediaViewController).closeGuts(false) } @Test fun cancelButtonClick_animation() { - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) cancel.callOnClick() @@ -502,7 +448,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun settingsButtonClick() { - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) settings.callOnClick() @@ -515,7 +461,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun dismissButtonClick() { val mediaKey = "key for dismissal" - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) val state = mediaData.copy(notificationKey = KEY) player.bindPlayer(state, mediaKey) @@ -527,7 +473,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun dismissButtonDisabled() { val mediaKey = "key for dismissal" - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) val state = mediaData.copy(isClearable = false, notificationKey = KEY) player.bindPlayer(state, mediaKey) @@ -539,7 +485,7 @@ public class MediaControlPanelTest : SysuiTestCase() { val mediaKey = "key for dismissal" whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false) - player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) + player.attachPlayer(viewHolder) val state = mediaData.copy(notificationKey = KEY) player.bindPlayer(state, mediaKey) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt deleted file mode 100644 index d6849bf2aa39..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media - -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.FrameLayout - -import androidx.test.filters.SmallTest - -import com.android.systemui.SysuiTestCase -import com.google.common.truth.Truth.assertThat - -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Tests for PlayerViewHolder. - */ -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class PlayerViewHolderTest : SysuiTestCase() { - - private lateinit var inflater: LayoutInflater - private lateinit var parent: ViewGroup - - @Before - fun setUp() { - inflater = LayoutInflater.from(context) - parent = FrameLayout(context) - } - - @Test - fun create() { - val holder = PlayerViewHolder.create(inflater, parent) - assertThat(holder.player).isNotNull() - } - - @Test - fun backgroundIsIlluminationDrawable() { - val holder = PlayerViewHolder.create(inflater, parent) - assertThat(holder.player.background as IlluminationDrawable).isNotNull() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt index 7ac15125ea7e..99901a0cd0b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt @@ -43,7 +43,7 @@ class SeekBarObserverTest : SysuiTestCase() { private val enabledHeight = 2 private lateinit var observer: SeekBarObserver - @Mock private lateinit var mockHolder: PlayerViewHolder + @Mock private lateinit var mockHolder: MediaViewHolder @Mock private lateinit var mockSquigglyProgress: SquigglyProgress private lateinit var seekBarView: SeekBar private lateinit var elapsedTimeView: TextView @@ -64,10 +64,8 @@ class SeekBarObserverTest : SysuiTestCase() { elapsedTimeView = TextView(context) totalTimeView = TextView(context) whenever(mockHolder.seekBar).thenReturn(seekBarView) - whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView) - whenever(mockHolder.totalTimeView).thenReturn(totalTimeView) - observer = SeekBarObserver(mockHolder, false /* useSessionLayout */) + observer = SeekBarObserver(mockHolder) } @Test @@ -79,8 +77,6 @@ class SeekBarObserverTest : SysuiTestCase() { // THEN seek bar shows just a thin line with no text assertThat(seekBarView.isEnabled()).isFalse() assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0) - assertThat(elapsedTimeView.getText()).isEqualTo("") - assertThat(totalTimeView.getText()).isEqualTo("") assertThat(seekBarView.contentDescription).isEqualTo("") assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight) } @@ -93,8 +89,6 @@ class SeekBarObserverTest : SysuiTestCase() { observer.onChanged(data) // THEN seek bar is visible and thick assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE) - assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.VISIBLE) - assertThat(totalTimeView.getVisibility()).isEqualTo(View.VISIBLE) assertThat(seekBarView.maxHeight).isEqualTo(enabledHeight) } @@ -106,8 +100,6 @@ class SeekBarObserverTest : SysuiTestCase() { // THEN seek bar shows the progress assertThat(seekBarView.progress).isEqualTo(3000) assertThat(seekBarView.max).isEqualTo(120000) - assertThat(elapsedTimeView.getText()).isEqualTo("00:03") - assertThat(totalTimeView.getText()).isEqualTo("02:00") val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00") assertThat(seekBarView.contentDescription).isEqualTo(desc) diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index 6df56e98783c..51088b1d929b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -123,7 +123,7 @@ public class ColorSchemeTest extends SysuiTestCase { Style.VIBRANT /* style */); int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); Cam cam = Cam.fromInt(neutralMid); - Assert.assertEquals(cam.getChroma(), 8.0, 1.0); + Assert.assertTrue(cam.getChroma() <= 8.0); } @Test @@ -133,7 +133,7 @@ public class ColorSchemeTest extends SysuiTestCase { Style.EXPRESSIVE /* style */); int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); Cam cam = Cam.fromInt(neutralMid); - Assert.assertEquals(cam.getChroma(), 12.0, 1.0); + Assert.assertTrue(cam.getChroma() <= 8.0); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index bb42c1277b90..4e3bdea4edfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -75,7 +75,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Mock private CommandQueue mCommandQueue; @Mock - private NavigationBar.Factory mNavigationBarFactory; + private NavigationBarComponent.Factory mNavigationBarFactory; @Before public void setUp() { 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 a0aa267e850e..f5b006d732fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -29,8 +29,6 @@ import static android.view.WindowInsets.Type.ime; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; -import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -54,7 +52,6 @@ import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; -import android.provider.Settings; import android.telecom.TelecomManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -82,26 +79,31 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; +import com.android.systemui.navigationbar.buttons.ButtonDispatcher; +import com.android.systemui.navigationbar.buttons.DeadZone; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.UserTracker; +import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.LightBarTransitionsController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.utils.leaks.LeakCheckedTest; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -122,12 +124,34 @@ public class NavigationBarTest extends SysuiTestCase { private SysuiTestableContext mSysuiTestableContextExternal; @Mock + NavigationBarFrame mNavigationBarFrame; + @Mock + NavigationBarView mNavigationBarView; + @Mock + ButtonDispatcher mHomeButton; + @Mock + ButtonDispatcher mRecentsButton; + @Mock + ButtonDispatcher mAccessibilityButton; + @Mock + ButtonDispatcher mImeSwitchButton; + @Mock + ButtonDispatcher mBackButton; + @Mock + NavigationBarTransitions mNavigationBarTransitions; + @Mock + RotationButtonController mRotationButtonController; + @Mock + LightBarTransitionsController mLightBarTransitionsController; + @Mock private SystemActions mSystemActions; @Mock private OverviewProxyService mOverviewProxyService; @Mock private StatusBarStateController mStatusBarStateController; @Mock + private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + @Mock private NavigationModeController mNavigationModeController; @Mock private CommandQueue mCommandQueue; @@ -160,7 +184,10 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private AssistManager mAssistManager; @Mock + private DeadZone mDeadZone; + @Mock private CentralSurfaces mCentralSurfaces; + private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake(); @Rule public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); @@ -173,6 +200,17 @@ public class NavigationBarTest extends SysuiTestCase { .thenReturn(mEdgeBackGestureHandler); when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController); when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController); + when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton); + when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton); + when(mNavigationBarView.getAccessibilityButton()).thenReturn(mAccessibilityButton); + when(mNavigationBarView.getImeSwitchButton()).thenReturn(mImeSwitchButton); + when(mNavigationBarView.getBackButton()).thenReturn(mBackButton); + when(mNavigationBarView.getBarTransitions()).thenReturn(mNavigationBarTransitions); + when(mNavigationBarView.getRotationButtonController()) + .thenReturn(mRotationButtonController); + when(mNavigationBarView.getLightTransitionsController()) + .thenReturn(mLightBarTransitionsController); + when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true); setupSysuiDependency(); // This class inflates views that call Dependency.get, thus these injections are still // necessary. @@ -197,12 +235,6 @@ public class NavigationBarTest extends SysuiTestCase { }); } - @After - public void tearDown() throws Exception { - DeviceConfig.resetToDefaults( - Settings.RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_SYSTEMUI); - } - private void setupSysuiDependency() { Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); @@ -228,8 +260,8 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testHomeLongPress() { - mNavigationBar.onViewAttachedToWindow(mNavigationBar - .createView(null, /* initialVisibility= */ true)); + mNavigationBar.init(); + mNavigationBar.onViewAttached(); mNavigationBar.onHomeLongClick(mNavigationBar.getView()); verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS); @@ -237,13 +269,14 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testHomeLongPressWithCustomDuration() throws Exception { - DeviceConfig.setProperties( - new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI) - .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100) - .build()); + mDeviceConfigProxyFake.setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, + HOME_BUTTON_LONG_PRESS_DURATION_MS, + "100", + false); when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true); - mNavigationBar.onViewAttachedToWindow(mNavigationBar - .createView(null, /* initialVisibility= */ true)); + mNavigationBar.init(); + mNavigationBar.onViewAttached(); mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain( /*downTime=*/SystemClock.uptimeMillis(), @@ -265,8 +298,8 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testRegisteredWithDispatcher() { - mNavigationBar.onViewAttachedToWindow(mNavigationBar - .createView(null, /* initialVisibility= */ true)); + mNavigationBar.init(); + mNavigationBar.onViewAttached(); verify(mBroadcastDispatcher).registerReceiverWithHandler( any(BroadcastReceiver.class), any(IntentFilter.class), @@ -286,8 +319,8 @@ public class NavigationBarTest extends SysuiTestCase { doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); doNothing().when(defaultNavBar).checkNavBarModes(); doNothing().when(externalNavBar).checkNavBarModes(); - defaultNavBar.createView(null, /* initialVisibility= */ true); - externalNavBar.createView(null, /* initialVisibility= */ true); + defaultNavBar.init(); + externalNavBar.init(); defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); @@ -321,7 +354,7 @@ public class NavigationBarTest extends SysuiTestCase { doReturn(mockShadeWindowView).when(mCentralSurfaces).getNotificationShadeWindowView(); doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); doNothing().when(mNavigationBar).checkNavBarModes(); - mNavigationBar.createView(null, /* initialVisibility= */ true); + mNavigationBar.init(); WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build(); doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); @@ -357,11 +390,11 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testA11yEventAfterDetach() { - View v = mNavigationBar.createView(null, /* initialVisibility= */ true); - mNavigationBar.onViewAttachedToWindow(v); + mNavigationBar.init(); + mNavigationBar.onViewAttached(); verify(mNavBarHelper).registerNavTaskStateUpdater(any( NavBarHelper.NavbarTaskbarStateUpdater.class)); - mNavigationBar.onViewDetachedFromWindow(v); + mNavigationBar.onViewDetached(); verify(mNavBarHelper).removeNavTaskStateUpdater(any( NavBarHelper.NavbarTaskbarStateUpdater.class)); @@ -371,23 +404,31 @@ public class NavigationBarTest extends SysuiTestCase { @Test public void testCreateView_initiallyVisible_viewIsVisible() { - mNavigationBar.createView(null, /* initialVisibility= */ true); + when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true); + mNavigationBar.init(); + mNavigationBar.onViewAttached(); - assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.VISIBLE); + verify(mNavigationBarView).setVisibility(View.VISIBLE); } @Test public void testCreateView_initiallyNotVisible_viewIsNotVisible() { - mNavigationBar.createView(null, /* initialVisibility= */ false); + when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(false); + mNavigationBar.init(); + mNavigationBar.onViewAttached(); - assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.INVISIBLE); + verify(mNavigationBarView).setVisibility(View.INVISIBLE); } private NavigationBar createNavBar(Context context) { DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true); - return spy(new NavigationBar(context, + return spy(new NavigationBar( + mNavigationBarView, + mNavigationBarFrame, + null, + context, mWindowManager, () -> mAssistManager, mock(AccessibilityManager.class), @@ -396,6 +437,7 @@ public class NavigationBarTest extends SysuiTestCase { mOverviewProxyService, mNavigationModeController, mStatusBarStateController, + mStatusBarKeyguardViewManager, mMockSysUiState, mBroadcastDispatcher, mCommandQueue, @@ -415,6 +457,8 @@ public class NavigationBarTest extends SysuiTestCase { mAutoHideControllerFactory, Optional.of(mTelecomManager), mInputMethodManager, + mDeadZone, + mDeviceConfigProxyFake, Optional.of(mock(BackAnimation.class)))); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt new file mode 100644 index 000000000000..bf82e90e88f6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt @@ -0,0 +1,80 @@ +package com.android.systemui.qs + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.customize.QSCustomizer +import com.android.systemui.util.mockito.eq +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +@SmallTest +class QSContainerImplTest : SysuiTestCase() { + + @Mock + private lateinit var quickStatusBarHeader: QuickStatusBarHeader + @Mock + private lateinit var qsCustomizer: QSCustomizer + @Mock + private lateinit var qsPanelContainer: NonInterceptingScrollView + @Mock + private lateinit var qsPanelController: QSPanelController + @Mock + private lateinit var quickStatusBarHeaderController: QuickStatusBarHeaderController + + private lateinit var qsContainer: QSContainerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + qsContainer = QSContainerImpl(mContext, null) + + setUpMockView(quickStatusBarHeader, R.id.header) + setUpMockView(qsCustomizer, R.id.qs_customize) + setUpMockView(qsPanelContainer, R.id.expanded_qs_scroll_view) + + qsContainer.onFinishInflate() + } + + private fun setUpMockView(view: View, id: Int) { + whenever(view.findViewById<View>(id)).thenReturn(view) + whenever(view.layoutParams).thenReturn(FrameLayout.LayoutParams(0, 0)) + qsContainer.addView(view) + } + + @Test + fun testContainerBottomPadding() { + qsContainer.updateResources( + qsPanelController, + quickStatusBarHeaderController, + /* newFooter */ false + ) + verify(qsPanelContainer).setPaddingRelative(anyInt(), anyInt(), anyInt(), eq(0)) + + qsContainer.updateResources( + qsPanelController, + quickStatusBarHeaderController, + /* newFooter */ true + ) + verify(qsPanelContainer) + .setPaddingRelative( + anyInt(), + anyInt(), + anyInt(), + eq(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height)) + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index ac1e86f58f86..1b48a16740b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -8,7 +8,6 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.media.MediaFlags import com.android.systemui.media.MediaHost import com.android.systemui.media.MediaHostState import com.android.systemui.plugins.FalsingManager @@ -49,7 +48,6 @@ class QSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var brightnessSliderFactory: BrightnessSliderController.Factory @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var featureFlags: FeatureFlags - @Mock private lateinit var mediaFlags: MediaFlags @Mock private lateinit var mediaHost: MediaHost private lateinit var controller: QSPanelController @@ -79,8 +77,7 @@ class QSPanelControllerTest : SysuiTestCase() { brightnessControllerFactory, brightnessSliderFactory, falsingManager, - featureFlags, - mediaFlags + featureFlags ) } @@ -90,45 +87,12 @@ class QSPanelControllerTest : SysuiTestCase() { } @Test - fun onInit_notSplitShade_newMediaLayoutAvailable_setsMediaAsExpanded() { - setSplitShadeEnabled(false) - whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true) - + fun onInit_setsMediaAsExpanded() { controller.onInit() verify(mediaHost).expansion = MediaHostState.EXPANDED } - @Test - fun onInit_notSplitShade_newMediaLayoutNotAvailable_setsMediaAsExpanded() { - setSplitShadeEnabled(false) - whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false) - - controller.onInit() - - verify(mediaHost).expansion = MediaHostState.EXPANDED - } - - @Test - fun onInit_inSplitShade_newMediaLayoutAvailable_setsMediaAsExpanded() { - setSplitShadeEnabled(true) - whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true) - - controller.onInit() - - verify(mediaHost).expansion = MediaHostState.EXPANDED - } - - @Test - fun onInit_inSplitShade_newMediaLayoutNotAvailable_setsMediaAsCollapsed() { - setSplitShadeEnabled(true) - whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false) - - controller.onInit() - - verify(mediaHost).expansion = MediaHostState.COLLAPSED - } - private fun setSplitShadeEnabled(enabled: Boolean) { mContext.orCreateTestableResources .addOverride(R.bool.config_use_split_notification_shade, enabled) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index 5213a30cfd59..04bbd60b8d90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -137,20 +137,6 @@ class QSPanelTest : SysuiTestCase() { assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1) } - @Test - fun testBottomPadding() { - mQsPanel.setUseNewFooter(false) - - mQsPanel.updatePadding() - assertThat(mQsPanel.paddingBottom).isEqualTo(0) - - mQsPanel.setUseNewFooter(true) - - mQsPanel.updatePadding() - assertThat(mQsPanel.paddingBottom) - .isEqualTo(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height)) - } - private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) = context.resources.configuration.apply { orientation = newOrientation } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 62915b8ac7c9..1f28210acc64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -23,7 +23,6 @@ import com.android.internal.logging.MetricsLogger import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.media.MediaFlags import com.android.systemui.media.MediaHost import com.android.systemui.media.MediaHostState import com.android.systemui.plugins.qs.QSTile @@ -58,8 +57,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost @Mock - private lateinit var mediaFlags: MediaFlags - @Mock private lateinit var metricsLogger: MetricsLogger private val uiEventLogger = UiEventLoggerFake() @Mock @@ -85,7 +82,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { `when`(quickQSPanel.dumpableTag).thenReturn("") `when`(quickQSPanel.resources).thenReturn(mContext.resources) `when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView) - `when`(mediaFlags.useMediaSessionLayout()).thenReturn(false) controller = TestQuickQSPanelController( quickQSPanel, @@ -94,7 +90,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { false, mediaHost, true, - mediaFlags, metricsLogger, uiEventLogger, qsLogger, @@ -131,20 +126,17 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Test fun testMediaExpansionUpdatedWhenConfigurationChanged() { - `when`(mediaFlags.useMediaSessionLayout()).thenReturn(true) - // times(2) because both controller and base controller are registering their listeners verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture()) - captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) } + // verify that media starts in the expanded state by default verify(mediaHost).expansion = MediaHostState.EXPANDED // Rotate device, verify media size updated controller.setRotation(RotationUtils.ROTATION_LANDSCAPE) captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) } - // times(2) because init will have set to collapsed because the flag was off - verify(mediaHost, times(2)).expansion = MediaHostState.COLLAPSED + verify(mediaHost).expansion = MediaHostState.COLLAPSED } class TestQuickQSPanelController( @@ -154,13 +146,12 @@ class QuickQSPanelControllerTest : SysuiTestCase() { usingMediaPlayer: Boolean, mediaHost: MediaHost, usingCollapsedLandscapeMedia: Boolean, - mediaFlags: MediaFlags, metricsLogger: MetricsLogger, uiEventLogger: UiEventLoggerFake, qsLogger: QSLogger, dumpManager: DumpManager ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer, - mediaHost, usingCollapsedLandscapeMedia, mediaFlags, metricsLogger, uiEventLogger, qsLogger, + mediaHost, usingCollapsedLandscapeMedia, metricsLogger, uiEventLogger, qsLogger, dumpManager) { private var rotation = RotationUtils.ROTATION_NONE 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 de9ea27e7c13..32d625c707cf 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,84 +22,238 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_MIN; 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.Mockito.spy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; import android.os.Handler; import android.os.UserHandle; +import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.CoreStartable; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton; +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.RankingBuilder; -import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.utils.os.FakeHandler; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Map; +import java.util.function.Consumer; + +import dagger.BindsInstance; +import dagger.Component; + @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { private static final int NOTIF_USER_ID = 0; private static final int CURR_USER_ID = 1; - @Mock - private Handler mMainHandler; @Mock private KeyguardStateController mKeyguardStateController; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider; - @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; @Mock private StatusBarStateController mStatusBarStateController; @Mock private BroadcastDispatcher mBroadcastDispatcher; + private final FakeSettings mFakeSettings = new FakeSettings(); + private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; private NotificationEntry mEntry; @Before public void setup() { MockitoAnnotations.initMocks(this); - // TODO refactor the test of KeyguardNotificationVisibilityProvider out - mKeyguardNotificationVisibilityProvider = spy(new KeyguardNotificationVisibilityProvider( - mContext, - mMainHandler, - mKeyguardStateController, - mLockscreenUserManager, - mKeyguardUpdateMonitor, - mHighPriorityProvider, - mStatusBarStateController, - mBroadcastDispatcher - )); - + TestComponent component = + DaggerKeyguardNotificationVisibilityProviderTest_TestComponent + .factory() + .create( + mContext, + new FakeHandler(TestableLooper.get(this).getLooper()), + mKeyguardStateController, + mLockscreenUserManager, + mKeyguardUpdateMonitor, + mHighPriorityProvider, + mStatusBarStateController, + mBroadcastDispatcher, + mFakeSettings, + mFakeSettings); + mKeyguardNotificationVisibilityProvider = component.getProvider(); + for (CoreStartable startable : component.getCoreStartables().values()) { + startable.start(); + } mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) .build(); } @Test + public void notifyListeners_onUnlockedChanged() { + ArgumentCaptor<KeyguardStateController.Callback> callbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + verify(mKeyguardStateController).addCallback(callbackCaptor.capture()); + KeyguardStateController.Callback callback = callbackCaptor.getValue(); + + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onUnlockedChanged(); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onKeyguardShowingChanged() { + ArgumentCaptor<KeyguardStateController.Callback> callbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + verify(mKeyguardStateController).addCallback(callbackCaptor.capture()); + KeyguardStateController.Callback callback = callbackCaptor.getValue(); + + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onKeyguardShowingChanged(); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onStrongAuthStateChanged() { + ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture()); + KeyguardUpdateMonitorCallback callback = callbackCaptor.getValue(); + + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onStrongAuthStateChanged(0); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onStatusBarStateChanged() { + ArgumentCaptor<StatusBarStateController.StateListener> callbackCaptor = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); + verify(mStatusBarStateController).addCallback(callbackCaptor.capture()); + StatusBarStateController.StateListener callback = callbackCaptor.getValue(); + + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onStateChanged(0); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onReceiveUserSwitchBroadcast() { + ArgumentCaptor<BroadcastReceiver> callbackCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mBroadcastDispatcher).registerReceiver( + callbackCaptor.capture(), + argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)), + isNull(), + isNull(), + eq(Context.RECEIVER_EXPORTED)); + BroadcastReceiver callback = callbackCaptor.getValue(); + + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED)); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_lockScreenShowNotifs() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_lockScreenAllowPrivateNotifs() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_zenMode() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Global.ZEN_MODE, 1); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_lockScreenShowSilentNotifs() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer<String> listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1); + + verify(listener).accept(anyString()); + } + + @Test public void unfilteredState() { // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); // THEN don't filter out the entry - assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -109,7 +263,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mKeyguardStateController.isShowing()).thenReturn(false); // THEN don't filter out the entry - assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -121,7 +275,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -133,7 +287,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -148,7 +302,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .thenReturn(false); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -164,7 +318,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .setVisibilityOverride(VISIBILITY_SECRET).build()); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -180,7 +334,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -209,7 +363,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true); // THEN don't filter out the entry - assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(entryWithParent)); + assertFalse( + mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent)); // WHEN its parent doesn't exceed threshold to show on lockscreen when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(false); @@ -218,7 +373,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .done()); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(entryWithParent)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent)); } /** @@ -259,4 +414,27 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { // notification is high priority, so it shouldn't be filtered when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true); } -} + + @SysUISingleton + @Component(modules = { KeyguardNotificationVisibilityProviderModule.class }) + interface TestComponent { + KeyguardNotificationVisibilityProvider getProvider(); + Map<Class<?>, CoreStartable> getCoreStartables(); + + @Component.Factory + interface Factory { + TestComponent create( + @BindsInstance Context context, + @BindsInstance @Main Handler handler, + @BindsInstance KeyguardStateController keyguardStateController, + @BindsInstance NotificationLockscreenUserManager lockscreenUserManager, + @BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor, + @BindsInstance HighPriorityProvider highPriorityProvider, + @BindsInstance StatusBarStateController 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/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt index 6526fabefe49..9e7b6c514ca3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt @@ -18,6 +18,8 @@ import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -31,6 +33,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.doNothing import org.mockito.Mockito.eq import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.function.Consumer @@ -71,6 +74,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { private lateinit var navigationModeCallback: ModeChangedListener private lateinit var taskbarVisibilityCallback: OverviewProxyListener private lateinit var windowInsetsCallback: Consumer<WindowInsets> + private lateinit var delayableExecutor: FakeExecutor + private lateinit var fakeSystemClock: FakeSystemClock @Before fun setup() { @@ -78,11 +83,14 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { mContext.ensureTestableResources() whenever(notificationsQSContainer.context).thenReturn(mContext) whenever(notificationsQSContainer.resources).thenReturn(mContext.resources) + fakeSystemClock = FakeSystemClock() + delayableExecutor = FakeExecutor(fakeSystemClock) controller = NotificationsQSContainerController( notificationsQSContainer, navigationModeController, overviewProxyService, - featureFlags + featureFlags, + delayableExecutor ) overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) @@ -490,13 +498,32 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { container.addView(newViewWithId(1)) container.addView(newViewWithId(View.NO_ID)) val controller = NotificationsQSContainerController(container, navigationModeController, - overviewProxyService, featureFlags) + overviewProxyService, featureFlags, delayableExecutor) controller.updateResources() assertThat(container.getChildAt(0).id).isEqualTo(1) assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID) } + @Test + fun testWindowInsetDebounce() { + disableSplitShade() + useNewFooter(true) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = emptyInsets(), + applyImmediately = false) + fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2) + windowInsetsCallback.accept(windowInsets().withStableBottom()) + + delayableExecutor.advanceClockToLast() + delayableExecutor.runAllReady() + + verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0) + verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM) + } + private fun disableSplitShade() { setSplitShadeEnabled(false) } @@ -513,12 +540,17 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { private fun given( taskbarVisible: Boolean, navigationMode: Int, - insets: WindowInsets + insets: WindowInsets, + applyImmediately: Boolean = true ) { Mockito.clearInvocations(notificationsQSContainer) taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false) navigationModeCallback.onNavigationModeChanged(navigationMode) windowInsetsCallback.accept(insets) + if (applyImmediately) { + delayableExecutor.advanceClockToLast() + delayableExecutor.runAllReady() + } } fun then( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index f4f55ccefd09..29488f1ba8a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -95,6 +95,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; @Mock + private KeyguardMessageAreaController mKeyguardMessageAreaController; + @Mock private KeyguardBouncer mBouncer; @Mock private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor; @@ -120,6 +122,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(mBouncer); when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer); when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea); + when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class))) + .thenReturn(mKeyguardMessageAreaController); mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager( getContext(), mViewMediatorCallback, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 589aa0353870..7c05c69ff3b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -14,10 +14,12 @@ package com.android.systemui.statusbar.phone; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.BroadcastReceiver; @@ -43,7 +45,6 @@ import org.mockito.MockitoAnnotations; @SmallTest public class SystemUIDialogTest extends SysuiTestCase { - private SystemUIDialog mDialog; @Mock private BroadcastDispatcher mBroadcastDispatcher; @@ -52,12 +53,11 @@ public class SystemUIDialogTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher); - - mDialog = new SystemUIDialog(mContext); } @Test public void testRegisterReceiver() { + final SystemUIDialog mDialog = new SystemUIDialog(mContext); final ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); final ArgumentCaptor<IntentFilter> intentFilterCaptor = @@ -66,10 +66,24 @@ public class SystemUIDialogTest extends SysuiTestCase { mDialog.show(); verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverCaptor.capture(), intentFilterCaptor.capture(), eq(null), any()); - + assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_SCREEN_OFF)); assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); mDialog.dismiss(); verify(mBroadcastDispatcher).unregisterReceiver(eq(broadcastReceiverCaptor.getValue())); } + + + @Test + public void testNoRegisterReceiver() { + final SystemUIDialog mDialog = new SystemUIDialog(mContext, false); + + mDialog.show(); + verify(mBroadcastDispatcher, never()).registerReceiver(any(), any(), eq(null), any()); + assertTrue(mDialog.isShowing()); + + mDialog.dismiss(); + verify(mBroadcastDispatcher, never()).unregisterReceiver(any()); + assertFalse(mDialog.isShowing()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index 0f1b65cb7f04..309acdf13a5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -24,6 +24,7 @@ package com.android.systemui.util.mockito */ import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatcher import org.mockito.Mockito /** @@ -44,6 +45,14 @@ fun <T> any(type: Class<T>): T = Mockito.any<T>(type) inline fun <reified T> any(): T = any(T::class.java) /** + * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when + * null is returned. + * + * Generic T is nullable because implicitly bounded by Any?. + */ +fun <T> argThat(matcher: ArgumentMatcher<T>): T = Mockito.argThat(matcher) + +/** * Kotlin type-inferred version of Mockito.nullable() */ inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index e251bcc39f32..c637045589e7 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -18,7 +18,6 @@ package com.android.server.accessibility; import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_BY_ADMIN; import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS; -import static android.content.pm.PackageManagerInternal.PACKAGE_INSTALLER; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -31,9 +30,7 @@ import android.app.admin.DevicePolicyManager; import android.appwidget.AppWidgetManagerInternal; import android.content.ComponentName; import android.content.Context; -import android.content.pm.InstallSourceInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; @@ -42,14 +39,12 @@ import android.os.IBinder; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import android.text.TextUtils; import android.util.ArraySet; import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.InputMethodInfo; import com.android.internal.util.ArrayUtils; -import com.android.server.LocalServices; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.settingslib.RestrictedLockUtils; @@ -734,7 +729,7 @@ public class AccessibilitySecurityPolicy { final AccessibilityServiceInfo a11yServiceInfo = boundServices.get( i).getServiceInfo(); final ComponentName service = a11yServiceInfo.getComponentName().clone(); - if (!isA11yCategoryService(a11yServiceInfo)) { + if (!a11yServiceInfo.isAccessibilityTool()) { tempNonA11yCategoryServices.add(service); if (mNonA11yCategoryServices.contains(service)) { mNonA11yCategoryServices.remove(service); @@ -794,69 +789,4 @@ public class AccessibilitySecurityPolicy { mPolicyWarningUIController.onEnabledServicesChangedLocked(userId, enabledServices); } - - /** - * Identifies whether the accessibility service is true and designed for accessibility. An - * accessibility service is considered as accessibility category if meets all conditions below: - * <ul> - * <li> {@link AccessibilityServiceInfo#isAccessibilityTool} is true</li> - * <li> is installed from the trusted install source</li> - * </ul> - * - * @param serviceInfo The accessibility service's serviceInfo. - * @return Returns true if it is a true accessibility service. - */ - public boolean isA11yCategoryService(AccessibilityServiceInfo serviceInfo) { - if (!serviceInfo.isAccessibilityTool()) { - return false; - } - if (!serviceInfo.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) { - return hasTrustedSystemInstallSource( - serviceInfo.getResolveInfo().serviceInfo.packageName); - } - return true; - } - - /** Returns true if the {@code installedPackage} is installed from the trusted install source. - */ - private boolean hasTrustedSystemInstallSource(String installedPackage) { - try { - InstallSourceInfo installSourceInfo = mPackageManager.getInstallSourceInfo( - installedPackage); - if (installSourceInfo == null) { - return false; - } - final String installSourcePackageName = installSourceInfo.getInitiatingPackageName(); - if (installSourcePackageName == null || !mPackageManager.getPackageInfo( - installSourcePackageName, - 0).applicationInfo.isSystemApp()) { - return false; - } - return isTrustedInstallSource(installSourcePackageName); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(LOG_TAG, "can't find the package's install source:" + installedPackage); - } - return false; - } - - /** Returns true if the {@code installerPackage} is a trusted install source. */ - private boolean isTrustedInstallSource(String installerPackage) { - final String[] allowedInstallingSources = mContext.getResources().getStringArray( - com.android.internal.R.array - .config_accessibility_allowed_install_source); - - if (allowedInstallingSources.length == 0) { - //Filters unwanted default installers if no allowed install sources. - String defaultInstaller = ArrayUtils.firstOrNull(LocalServices.getService( - PackageManagerInternal.class).getKnownPackageNames(PACKAGE_INSTALLER, - mCurrentUserId)); - return !TextUtils.equals(defaultInstaller, installerPackage); - } - for (int i = 0; i < allowedInstallingSources.length; i++) { - if (TextUtils.equals(allowedInstallingSources[i], installerPackage)) { - return true; - } - } - return false; - } } diff --git a/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java index 5080ca277445..7c12ece472c2 100644 --- a/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java +++ b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java @@ -270,7 +270,7 @@ public class PolicyWarningUIController { final AccessibilityServiceInfo a11yServiceInfo = enabledServiceInfos.get(i); if (componentName.flattenToShortString().equals( a11yServiceInfo.getComponentName().flattenToShortString())) { - if (!mAccessibilitySecurityPolicy.isA11yCategoryService(a11yServiceInfo) + if (!a11yServiceInfo.isAccessibilityTool() && !mNotifiedA11yServices.contains(componentName)) { final CharSequence displayName = a11yServiceInfo.getResolveInfo().serviceInfo.loadLabel( diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index e20b15a3e807..6846b2e3e6f7 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -1006,12 +1006,10 @@ public class TouchExplorer extends BaseEventStreamTransformation } break; case ACTION_POINTER_UP: - if (event.getPointerId(GestureUtils.getActionIndex(event)) == mDraggingPointerId) { mDraggingPointerId = INVALID_POINTER_ID; // Send an event to the end of the drag gesture. mDispatcher.sendMotionEvent( event, ACTION_UP, rawEvent, pointerIdBits, policyFlags); - } break; case ACTION_UP: if (event.getPointerId(GestureUtils.getActionIndex(event)) == mDraggingPointerId) { @@ -1146,6 +1144,10 @@ public class TouchExplorer extends BaseEventStreamTransformation * closet to an edge of the screen. */ private void computeDraggingPointerIdIfNeeded(MotionEvent event) { + if (event.getPointerCount() != 2) { + mDraggingPointerId = INVALID_POINTER_ID; + return; + } if (mDraggingPointerId != INVALID_POINTER_ID) { // If we have a valid pointer ID, we should be good final int pointerIndex = event.findPointerIndex(mDraggingPointerId); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index a65d5b3b94f7..312105a42b30 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -1617,14 +1617,17 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final int providerCount = mProviders.size(); for (int i = 0; i < providerCount; i++) { Provider provider = mProviders.get(i); - AppWidgetProviderInfo info = provider.getInfoLocked(mContext); final String providerPackageName = provider.id.componentName.getPackageName(); - // Ignore an invalid provider, one not matching the filter, - // or one that isn't in the given package, if any. - boolean inPackage = packageName == null - || providerPackageName.equals(packageName); - if (provider.zombie || (info.widgetCategory & categoryFilter) == 0 || !inPackage) { + // Ignore an invalid provider or one that isn't in the given package, if any. + boolean inPackage = packageName == null || providerPackageName.equals(packageName); + if (provider.zombie || !inPackage) { + continue; + } + + // Ignore the ones not matching the filter. + AppWidgetProviderInfo info = provider.getInfoLocked(mContext); + if ((info.widgetCategory & categoryFilter) == 0) { continue; } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index a2cfe4928249..af7dcd114066 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -38,7 +38,6 @@ import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED; import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT; import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT; -import static android.os.storage.OnObbStateChangeListener.ERROR_INTERNAL; import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED; import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED; import static android.os.storage.OnObbStateChangeListener.MOUNTED; @@ -598,12 +597,6 @@ class StorageManagerService extends IStorageManager.Stub } } - /** List of crypto types. - * These must match CRYPT_TYPE_XXX in cryptfs.h AND their - * corresponding commands in CommandListener.cpp */ - public static final String[] CRYPTO_TYPES - = { "password", "default", "pattern", "pin" }; - private final Context mContext; private final ContentResolver mResolver; @@ -622,18 +615,6 @@ class StorageManagerService extends IStorageManager.Stub private final Callbacks mCallbacks; private final LockPatternUtils mLockPatternUtils; - /** - * The size of the crypto algorithm key in bits for OBB files. Currently - * Twofish is used which takes 128-bit keys. - */ - private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128; - - /** - * The number of times to run SHA1 in the PBKDF2 function for OBB files. - * 1024 is reasonably secure and not too slow. - */ - private static final int PBKDF2_HASH_ROUNDS = 1024; - private static final String ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY = "anr_delay_millis"; diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index ed545a6f720c..206a3109e6a8 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -32,6 +32,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.UserManagerService; import java.io.PrintWriter; @@ -261,13 +262,23 @@ public abstract class SystemService { public @interface EventTypesFlag { } - private @EventTypesFlag int mEventType; + private final @EventTypesFlag int mEventType; /** @hide */ UserCompletedEventType(@EventTypesFlag int eventType) { mEventType = eventType; } + /** + * Creates a new instance of {@link UserCompletedEventType}. + * @hide + */ + @VisibleForTesting + public static UserCompletedEventType newUserCompletedEventTypeForTest( + @EventTypesFlag int eventType) { + return new UserCompletedEventType(eventType); + } + /** Returns whether one of the events is {@link #onUserStarting}. */ public boolean includesOnUserStarting() { return (mEventType & EVENT_TYPE_USER_STARTING) != 0; diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 2dfe94729bb8..2f84ec5489fd 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -362,11 +362,9 @@ final class UiModeManagerService extends SystemService { getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver); verifySetupWizardCompleted(); synchronized (mLock) { - // only update if the value is actually changed - if (updateNightModeFromSettingsLocked(getContext(), getContext().getResources(), - to.getUserIdentifier())) { - updateLocked(0, 0); - } + updateNightModeFromSettingsLocked(getContext(), getContext().getResources(), + to.getUserIdentifier()); + updateLocked(0, 0); } } @@ -534,19 +532,16 @@ final class UiModeManagerService extends SystemService { } /** - * Updates the night mode setting in Settings.Secure and returns if the value was successfully - * changed. + * Updates the night mode setting in Settings.Secure * * @param context A valid context * @param res A valid resource object * @param userId The user to update the setting for - * @return True if the new value is different from the old value. False otherwise. */ - private boolean updateNightModeFromSettingsLocked(Context context, Resources res, int userId) { + private void updateNightModeFromSettingsLocked(Context context, Resources res, int userId) { if (mCarModeEnabled || mCar) { - return false; + return; } - int oldNightMode = mNightMode; if (mSetupWizardComplete) { mNightMode = Secure.getIntForUser(context.getContentResolver(), Secure.UI_NIGHT_MODE, res.getInteger( @@ -570,8 +565,6 @@ final class UiModeManagerService extends SystemService { Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0; } } - - return oldNightMode != mNightMode; } private static long toMilliSeconds(LocalTime t) { diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 15484b2d618e..41ff08309d89 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -1841,6 +1841,9 @@ public final class AppRestrictionController { } void handleUidInactive(int uid, boolean disabled) { + if (!mConstantsObserver.mBgAutoRestrictedBucket) { + return; + } final ArrayList<Runnable> pendingTasks = mTmpRunnables; synchronized (mSettingsLock) { final int index = mActiveUids.indexOfKey(uid); @@ -1863,6 +1866,9 @@ public final class AppRestrictionController { } void handleUidActive(int uid) { + if (!mConstantsObserver.mBgAutoRestrictedBucket) { + return; + } synchronized (mSettingsLock) { final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal(); final int userId = UserHandle.getUserId(uid); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 309a4ff16753..a08ef4b5ab8d 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -226,6 +226,7 @@ public class AudioService extends IAudioService.Stub private final AudioSystemAdapter mAudioSystem; private final SystemServerAdapter mSystemServer; + private final SettingsAdapter mSettings; /** Debug audio mode */ protected static final boolean DEBUG_MODE = false; @@ -876,11 +877,12 @@ public class AudioService extends IAudioService.Stub /** @hide */ public AudioService(Context context) { this(context, AudioSystemAdapter.getDefaultAdapter(), - SystemServerAdapter.getDefaultAdapter(context)); + SystemServerAdapter.getDefaultAdapter(context), + SettingsAdapter.getDefaultAdapter()); } public AudioService(Context context, AudioSystemAdapter audioSystem, - SystemServerAdapter systemServer) { + SystemServerAdapter systemServer, SettingsAdapter settings) { sLifecycleLogger.log(new AudioEventLogger.StringEvent("AudioService()")); mContext = context; mContentResolver = context.getContentResolver(); @@ -888,6 +890,7 @@ public class AudioService extends IAudioService.Stub mAudioSystem = audioSystem; mSystemServer = systemServer; + mSettings = settings; mPlatformType = AudioSystem.getPlatformType(context); @@ -1024,7 +1027,7 @@ public class AudioService extends IAudioService.Stub new String("AudioService ctor"), 0); - mSafeMediaVolumeState = Settings.Global.getInt(mContentResolver, + mSafeMediaVolumeState = mSettings.getGlobalInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED); // The default safe volume index read here will be replaced by the actual value when @@ -2009,7 +2012,7 @@ public class AudioService extends IAudioService.Stub private void readDockAudioSettings(ContentResolver cr) { - mDockAudioMediaEnabled = Settings.Global.getInt( + mDockAudioMediaEnabled = mSettings.getGlobalInt( cr, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) == 1; sendMsg(mAudioHandler, @@ -2025,7 +2028,7 @@ public class AudioService extends IAudioService.Stub private void updateMasterMono(ContentResolver cr) { - final boolean masterMono = System.getIntForUser( + final boolean masterMono = mSettings.getSystemIntForUser( cr, System.MASTER_MONO, 0 /* default */, UserHandle.USER_CURRENT) == 1; if (DEBUG_VOL) { Log.d(TAG, String.format("Master mono %b", masterMono)); @@ -2046,7 +2049,7 @@ public class AudioService extends IAudioService.Stub private void sendEncodedSurroundMode(ContentResolver cr, String eventSource) { - final int encodedSurroundMode = Settings.Global.getInt( + final int encodedSurroundMode = mSettings.getGlobalInt( cr, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); sendEncodedSurroundMode(encodedSurroundMode, eventSource); @@ -2162,7 +2165,7 @@ public class AudioService extends IAudioService.Stub final long token = Binder.clearCallingIdentity(); try { synchronized (mSettingsLock) { - Settings.Global.putString(mContentResolver, + mSettings.putGlobalString(mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS, TextUtils.join(",", enabledFormats)); } @@ -2183,7 +2186,7 @@ public class AudioService extends IAudioService.Stub final long token = Binder.clearCallingIdentity(); try { synchronized (mSettingsLock) { - Settings.Global.putInt(mContentResolver, + mSettings.putGlobalInt(mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, toEncodedSurroundSetting(mode)); } @@ -2204,7 +2207,7 @@ public class AudioService extends IAudioService.Stub final long token = Binder.clearCallingIdentity(); try { synchronized (mSettingsLock) { - int encodedSurroundSetting = Settings.Global.getInt(mContentResolver, + int encodedSurroundSetting = mSettings.getGlobalInt(mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, AudioManager.ENCODED_SURROUND_OUTPUT_AUTO); return toEncodedSurroundOutputMode(encodedSurroundSetting, targetSdkVersion); @@ -2217,7 +2220,7 @@ public class AudioService extends IAudioService.Stub /** @return the formats that are enabled in global settings */ private HashSet<Integer> getEnabledFormats() { HashSet<Integer> formats = new HashSet<>(); - String enabledFormats = Settings.Global.getString(mContentResolver, + String enabledFormats = mSettings.getGlobalString(mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS); if (enabledFormats != null) { try { @@ -2280,7 +2283,7 @@ public class AudioService extends IAudioService.Stub // Manually enable surround formats only when the setting is in manual mode. return; } - String enabledSurroundFormats = Settings.Global.getString( + String enabledSurroundFormats = mSettings.getGlobalString( cr, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS); if (enabledSurroundFormats == null) { // Never allow enabledSurroundFormats as a null, which could happen when @@ -2308,7 +2311,7 @@ public class AudioService extends IAudioService.Stub } // Set filtered surround formats to settings DB in case // there are invalid surround formats in original settings. - Settings.Global.putString(mContext.getContentResolver(), + mSettings.putGlobalString(mContext.getContentResolver(), Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS, TextUtils.join(",", formats)); sendMsg(mAudioHandler, MSG_ENABLE_SURROUND_FORMATS, SENDMSG_QUEUE, 0, 0, formats, 0); @@ -2336,11 +2339,11 @@ public class AudioService extends IAudioService.Stub packageName = mRoleObserver.getAssistantRoleHolder(); } if (TextUtils.isEmpty(packageName)) { - String assistantName = Settings.Secure.getStringForUser( + String assistantName = mSettings.getSecureStringForUser( mContentResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, UserHandle.USER_CURRENT); if (TextUtils.isEmpty(assistantName)) { - assistantName = Settings.Secure.getStringForUser( + assistantName = mSettings.getSecureStringForUser( mContentResolver, Settings.Secure.ASSISTANT, UserHandle.USER_CURRENT); } @@ -2383,7 +2386,7 @@ public class AudioService extends IAudioService.Stub final ContentResolver cr = mContentResolver; int ringerModeFromSettings = - Settings.Global.getInt( + mSettings.getGlobalInt( cr, Settings.Global.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); int ringerMode = ringerModeFromSettings; // validity check in case the settings are restored from a device with incompatible @@ -2395,7 +2398,7 @@ public class AudioService extends IAudioService.Stub ringerMode = AudioManager.RINGER_MODE_SILENT; } if (ringerMode != ringerModeFromSettings) { - Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode); + mSettings.putGlobalInt(cr, Settings.Global.MODE_RINGER, ringerMode); } if (mUseFixedVolume || mIsSingleVolume) { ringerMode = AudioManager.RINGER_MODE_NORMAL; @@ -2427,7 +2430,7 @@ public class AudioService extends IAudioService.Stub AudioSystem.setRttEnabled(mRttEnabled); } - mMuteAffectedStreams = System.getIntForUser(cr, + mMuteAffectedStreams = mSettings.getSystemIntForUser(cr, System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED, UserHandle.USER_CURRENT); @@ -4480,7 +4483,7 @@ public class AudioService extends IAudioService.Stub int silenceRingerSetting = Settings.Secure.VOLUME_HUSH_OFF; if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled)) { - silenceRingerSetting = Settings.Secure.getIntForUser(mContentResolver, + silenceRingerSetting = mSettings.getSecureIntForUser(mContentResolver, Settings.Secure.VOLUME_HUSH_GESTURE, VOLUME_HUSH_OFF, UserHandle.USER_CURRENT); } @@ -5241,7 +5244,7 @@ public class AudioService extends IAudioService.Stub * Settings has an in memory cache, so this is fast. */ private boolean querySoundEffectsEnabled(int user) { - return Settings.System.getIntForUser(getContentResolver(), + return mSettings.getSystemIntForUser(getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0; } @@ -5326,7 +5329,7 @@ public class AudioService extends IAudioService.Stub checkMuteAffectedStreams(); synchronized (mSafeMediaVolumeStateLock) { - mMusicActiveMs = MathUtils.constrain(Settings.Secure.getIntForUser(mContentResolver, + mMusicActiveMs = MathUtils.constrain(mSettings.getSecureIntForUser(mContentResolver, Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT), 0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX); if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) { @@ -5968,7 +5971,7 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mSettingsLock") private boolean updateRingerAndZenModeAffectedStreams() { boolean updatedZenModeAffectedStreams = updateZenModeAffectedStreams(); - int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver, + int ringerModeAffectedStreams = mSettings.getSystemIntForUser(mContentResolver, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), @@ -5992,7 +5995,7 @@ public class AudioService extends IAudioService.Stub } if (ringerModeAffectedStreams != mRingerModeAffectedStreams) { - Settings.System.putIntForUser(mContentResolver, + mSettings.putSystemIntForUser(mContentResolver, Settings.System.MODE_RINGER_STREAMS_AFFECTED, ringerModeAffectedStreams, UserHandle.USER_CURRENT); @@ -6969,7 +6972,7 @@ public class AudioService extends IAudioService.Stub + ", device " + AudioSystem.getOutputDeviceName(device) + " and User=" + ActivityManager.getCurrentUser()); } - boolean success = Settings.System.putIntForUser(mContentResolver, + boolean success = mSettings.putSystemIntForUser(mContentResolver, getSettingNameForDevice(device), getIndex(device), UserHandle.USER_CURRENT); @@ -6995,7 +6998,7 @@ public class AudioService extends IAudioService.Stub ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1; int index; String name = getSettingNameForDevice(device); - index = Settings.System.getIntForUser( + index = mSettings.getSystemIntForUser( mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); if (index == -1) { continue; @@ -7244,7 +7247,7 @@ public class AudioService extends IAudioService.Stub index = defaultIndex; } else { String name = getSettingNameForDevice(device); - index = Settings.System.getIntForUser( + index = mSettings.getSystemIntForUser( mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); } if (index == -1) { @@ -7781,7 +7784,7 @@ public class AudioService extends IAudioService.Stub return; } if (streamState.hasValidSettingsName()) { - System.putIntForUser(mContentResolver, + mSettings.putSystemIntForUser(mContentResolver, streamState.getSettingNameForDevice(device), (streamState.getIndex(device) + 5)/ 10, UserHandle.USER_CURRENT); @@ -7792,11 +7795,11 @@ public class AudioService extends IAudioService.Stub if (mUseFixedVolume) { return; } - Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode); + mSettings.putGlobalInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode); } private void onPersistSafeVolumeState(int state) { - Settings.Global.putInt(mContentResolver, + mSettings.putGlobalInt(mContentResolver, Settings.Global.AUDIO_SAFE_VOLUME_STATE, state); } @@ -7940,7 +7943,7 @@ public class AudioService extends IAudioService.Stub case MSG_PERSIST_MUSIC_ACTIVE_MS: final int musicActiveMs = msg.arg1; - Settings.Secure.putIntForUser(mContentResolver, + mSettings.putSecureIntForUser(mContentResolver, Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs, UserHandle.USER_CURRENT); break; @@ -8081,12 +8084,12 @@ public class AudioService extends IAudioService.Stub mContentResolver.registerContentObserver(Settings.System.getUriFor( Settings.System.MASTER_BALANCE), false, this); - mEncodedSurroundMode = Settings.Global.getInt( + mEncodedSurroundMode = mSettings.getGlobalInt( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.ENCODED_SURROUND_OUTPUT), false, this); - mEnabledSurroundFormats = Settings.Global.getString( + mEnabledSurroundFormats = mSettings.getGlobalString( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS), false, this); @@ -8120,7 +8123,7 @@ public class AudioService extends IAudioService.Stub } private void updateEncodedSurroundOutput() { - int newSurroundMode = Settings.Global.getInt( + int newSurroundMode = mSettings.getGlobalInt( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO); // Did it change? @@ -8663,13 +8666,13 @@ public class AudioService extends IAudioService.Stub } void onPersistSpatialAudioEnabled(boolean enabled) { - Settings.Secure.putIntForUser(mContentResolver, + mSettings.putSecureIntForUser(mContentResolver, Settings.Secure.SPATIAL_AUDIO_ENABLED, enabled ? 1 : 0, UserHandle.USER_CURRENT); } boolean isSpatialAudioEnabled() { - return Settings.Secure.getIntForUser(mContentResolver, + return mSettings.getSecureIntForUser(mContentResolver, Settings.Secure.SPATIAL_AUDIO_ENABLED, SPATIAL_AUDIO_ENABLED_DEFAULT ? 1 : 0, UserHandle.USER_CURRENT) == 1; } @@ -9806,7 +9809,7 @@ public class AudioService extends IAudioService.Stub } public void loadSettings(ContentResolver cr) { - mLongPressTimeout = Settings.Secure.getIntForUser(cr, + mLongPressTimeout = mSettings.getSecureIntForUser(cr, Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT); } @@ -11430,7 +11433,7 @@ public class AudioService extends IAudioService.Stub } final long callingIdentity = Binder.clearCallingIdentity(); try { - System.putIntForUser(mContentResolver, + mSettings.putSystemIntForUser(mContentResolver, getSettingsNameForDeviceVolumeBehavior(deviceType), deviceVolumeBehavior, UserHandle.USER_CURRENT); @@ -11441,7 +11444,7 @@ public class AudioService extends IAudioService.Stub @AudioManager.DeviceVolumeBehaviorState private int retrieveStoredDeviceVolumeBehavior(int deviceType) { - return System.getIntForUser(mContentResolver, + return mSettings.getSystemIntForUser(mContentResolver, getSettingsNameForDeviceVolumeBehavior(deviceType), AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET, UserHandle.USER_CURRENT); diff --git a/services/core/java/com/android/server/audio/SettingsAdapter.java b/services/core/java/com/android/server/audio/SettingsAdapter.java new file mode 100644 index 000000000000..dbc4d6d1c676 --- /dev/null +++ b/services/core/java/com/android/server/audio/SettingsAdapter.java @@ -0,0 +1,92 @@ +/* + * 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.audio; + +import android.content.ContentResolver; +import android.provider.Settings; + +/** + * Adapter for methods that read and write settings in android.provider.Settings. + */ +public class SettingsAdapter { + public static SettingsAdapter getDefaultAdapter() { + return new SettingsAdapter(); + } + + /** + * Wrapper methods for Settings.Global + */ + + /** Wraps {@link Settings.Global#getInt(ContentResolver, String, int)} */ + public int getGlobalInt(ContentResolver cr, String name, int def) { + return Settings.Global.getInt(cr, name, def); + } + + /** Wraps {@link Settings.Global#getString(ContentResolver, String)} */ + public String getGlobalString(ContentResolver resolver, String name) { + return Settings.Global.getString(resolver, name); + } + + /** Wraps {@link Settings.Global#putInt(ContentResolver, String, int)} */ + public boolean putGlobalInt(ContentResolver cr, String name, int value) { + return Settings.Global.putInt(cr, name, value); + } + + /** Wraps {@link Settings.Global#putString(ContentResolver, String, String)} */ + public boolean putGlobalString(ContentResolver resolver, String name, String value) { + return Settings.Global.putString(resolver, name, value); + } + + /** + * Wrapper methods for Settings.System + */ + + /** Wraps {@link Settings.System#getIntForUser(ContentResolver, String, int, int)} */ + public int getSystemIntForUser(ContentResolver cr, String name, int def, int userHandle) { + return Settings.System.getIntForUser(cr, name, def, userHandle); + } + + /** Wraps {@link Settings.System#putIntForUser(ContentResolver, String, int, int)} */ + public boolean putSystemIntForUser(ContentResolver cr, String name, int value, int userHandle) { + return Settings.System.putIntForUser(cr, name, value, userHandle); + } + + /** + * Wrapper methods for Settings.Secure + */ + + /** Wraps {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */ + public int getSecureIntForUser(ContentResolver cr, String name, int def, int userHandle) { + return Settings.Secure.getIntForUser(cr, name, def, userHandle); + } + + /** Wraps {@link Settings.Secure#getStringForUser(ContentResolver, String, int)} */ + public String getSecureStringForUser(ContentResolver resolver, String name, int userHandle) { + return Settings.Secure.getStringForUser(resolver, name, userHandle); + } + + /** Wraps {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */ + public boolean putSecureIntForUser(ContentResolver cr, String name, int value, int userHandle) { + return Settings.Secure.putIntForUser(cr, name, value, userHandle); + } + + /** Wraps {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */ + public boolean putSecureStringForUser(ContentResolver cr, String name, String value, + int userHandle) { + return Settings.Secure.putStringForUser(cr, name, value, userHandle); + } +} diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java index 108e7bcb23bb..b9efdf551646 100644 --- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java @@ -71,7 +71,6 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -222,8 +221,10 @@ public class MultipathPolicyTracker { "Can't get TelephonyManager for subId %d", mSubId)); } - subscriberId = Objects.requireNonNull(tele.getSubscriberId(), - "Null subscriber Id for subId " + mSubId); + subscriberId = tele.getSubscriberId(); + if (subscriberId == null) { + throw new IllegalStateException("Null subscriber Id for subId " + mSubId); + } mNetworkTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE) .setSubscriberIds(Set.of(subscriberId)) .setMeteredness(NetworkStats.METERED_YES) diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 9067f2e25152..f819e56e6856 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -129,7 +129,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_STOP = 9; private static final int MSG_UPDATE_BRIGHTNESS = 10; private static final int MSG_UPDATE_RBC = 11; - private static final int MSG_STATSD_HBM_BRIGHTNESS = 12; + private static final int MSG_BRIGHTNESS_RAMP_DONE = 12; + private static final int MSG_STATSD_HBM_BRIGHTNESS = 13; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -1050,9 +1051,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Override public void onAnimationEnd() { sendUpdatePowerState(); - - final float brightness = mPowerState.getScreenBrightness(); - reportStats(brightness); + Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE); + mHandler.sendMessage(msg); } }; @@ -1066,6 +1066,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mCallbacks.releaseSuspendBlocker(); mUnfinishedBusiness = false; } + + final float brightness = mPowerState != null + ? mPowerState.getScreenBrightness() + : PowerManager.BRIGHTNESS_MIN; + reportStats(brightness); + if (mPowerState != null) { mPowerState.stop(); mPowerState = null; @@ -2580,6 +2586,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private void reportStats(float brightness) { + if (mLastStatsBrightness == brightness) { + return; + } + float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX; synchronized(mCachedBrightnessInfo) { if (mCachedBrightnessInfo.hbmTransitionPoint == null) { @@ -2680,6 +2690,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call handleRbcChanged(strengthChanged == 1, justActivated == 1); break; + case MSG_BRIGHTNESS_RAMP_DONE: + if (mPowerState != null) { + final float brightness = mPowerState.getScreenBrightness(); + reportStats(brightness); + } + break; + case MSG_STATSD_HBM_BRIGHTNESS: logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2); break; diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index e6fd40902386..e62c5c13f04d 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -245,4 +245,13 @@ final class IInputMethodInvoker { logRemoteException(e); } } + + @AnyThread + void finishStylusHandwriting() { + try { + mTarget.finishStylusHandwriting(); + } catch (RemoteException e) { + logRemoteException(e); + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 29dcdfaa1bba..a2d3588f0e68 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -19,6 +19,7 @@ package com.android.server.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodInfo; @@ -150,6 +151,12 @@ public abstract class InputMethodManagerInternal { public abstract void updateImeWindowStatus(boolean disableImeIcon); /** + * Finish stylus handwriting by calling {@link InputMethodService#finishStylusHandwriting()} if + * there is an ongoing handwriting session. + */ + public abstract void maybeFinishStylusHandwriting(); + + /** * Callback when the IInputMethodSession from the accessibility service with the specified * accessibilityConnectionId is created. * @@ -239,6 +246,10 @@ public abstract class InputMethodManagerInternal { @Override public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) { } + + @Override + public void maybeFinishStylusHandwriting() { + } }; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 77dcbd3e9277..d6131d162173 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -49,6 +49,7 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT; +import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -165,6 +166,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; +import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; @@ -178,6 +180,7 @@ import com.android.server.AccessibilityManagerInternal; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.ServiceThread; +import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener; import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; @@ -187,6 +190,8 @@ import com.android.server.statusbar.StatusBarManagerService; import com.android.server.utils.PriorityDump; import com.android.server.wm.WindowManagerInternal; +import com.google.android.collect.Sets; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -201,8 +206,10 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.OptionalInt; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; /** @@ -230,6 +237,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private static final int MSG_RESET_HANDWRITING = 1090; private static final int MSG_START_HANDWRITING = 1100; + private static final int MSG_FINISH_HANDWRITING = 1110; private static final int MSG_UNBIND_CLIENT = 3000; private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 3001; @@ -267,6 +275,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ private final boolean mPreventImeStartupUnlessTextEditor; + /** + * These IMEs are known not to behave well when evicted from memory and thus are exempt + * from the IME startup avoidance behavior that is enabled by + * {@link #mPreventImeStartupUnlessTextEditor}. + */ + @NonNull + private final Set<String> mNonPreemptibleInputMethods; + @UserIdInt private int mLastSwitchUserId; @@ -339,6 +355,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") @Nullable private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes; + @GuardedBy("ImfLock.class") + @Nullable + Future<?> mImeDrawsImeNavBarResLazyInitFuture; static class SessionState { final ClientState client; @@ -1692,6 +1711,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mBindingController = new InputMethodBindingController(this); mPreventImeStartupUnlessTextEditor = mRes.getBoolean( com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); + mNonPreemptibleInputMethods = Sets.newHashSet(mRes.getStringArray( + com.android.internal.R.array.config_nonPreemptibleInputMethods)); mHwController = new HandwritingModeController(thread.getLooper(), new InkWindowInitializer()); } @@ -1728,7 +1749,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - private void recreateImeDrawsImeNavBarResIfNecessary(@UserIdInt int targetUserId) { + private void maybeInitImeNavbarConfigLocked(@UserIdInt int targetUserId) { // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the // profile parent user. // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups. @@ -1772,7 +1793,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId + " currentUserId=" + mSettings.getCurrentUserId()); - recreateImeDrawsImeNavBarResIfNecessary(newUserId); + maybeInitImeNavbarConfigLocked(newUserId); // ContentObserver should be registered again when the user is changed mSettingsObserver.registerContentObserverLocked(newUserId); @@ -1878,7 +1899,22 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub }); } - recreateImeDrawsImeNavBarResIfNecessary(currentUserId); + // TODO(b/32343335): The entire systemRunning() method needs to be revisited. + mImeDrawsImeNavBarResLazyInitFuture = SystemServerInitThreadPool.submit(() -> { + // Note that the synchronization block below guarantees that the task + // can never be completed before the returned Future<?> object is assigned to + // the "mImeDrawsImeNavBarResLazyInitFuture" field. + synchronized (ImfLock.class) { + mImeDrawsImeNavBarResLazyInitFuture = null; + if (currentUserId != mSettings.getCurrentUserId()) { + // This means that the current user is already switched to other user + // before the background task is executed. In this scenario the relevant + // field should already be initialized. + return; + } + maybeInitImeNavbarConfigLocked(currentUserId); + } + }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes"); mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); mSettingsObserver.registerContentObserverLocked(currentUserId); @@ -2548,7 +2584,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, int unverifiedTargetSdkVersion) { // If no method is currently selected, do nothing. - String selectedMethodId = getSelectedMethodIdLocked(); + final String selectedMethodId = getSelectedMethodIdLocked(); if (selectedMethodId == null) { return InputBindResult.NO_IME; } @@ -2592,10 +2628,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurAttribute = attribute; // If configured, we want to avoid starting up the IME if it is not supposed to be showing - if (mPreventImeStartupUnlessTextEditor - && !InputMethodUtils.isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, - startInputFlags) - && !mShowRequested) { + if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags, + unverifiedTargetSdkVersion)) { if (DEBUG) { Slog.d(TAG, "Avoiding IME startup and unbinding current input method."); } @@ -2637,6 +2671,34 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") + private boolean shouldPreventImeStartupLocked( + @NonNull String selectedMethodId, + @StartInputFlags int startInputFlags, + int unverifiedTargetSdkVersion) { + // Fast-path for the majority of cases + if (!mPreventImeStartupUnlessTextEditor) { + return false; + } + + final boolean imeVisibleAllowed = + isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags); + + return !(imeVisibleAllowed + || mShowRequested + || isNonPreemptibleImeLocked(selectedMethodId)); + } + + /** Return {@code true} if the given IME is non-preemptible like the tv remote service. */ + @GuardedBy("ImfLock.class") + private boolean isNonPreemptibleImeLocked(@NonNull String selectedMethodId) { + final InputMethodInfo imi = mMethodMap.get(selectedMethodId); + if (imi != null) { + return mNonPreemptibleInputMethods.contains(imi.getPackageName()); + } + return false; + } + + @GuardedBy("ImfLock.class") private boolean isSelectedMethodBoundLocked() { String curId = getCurIdLocked(); return curId != null && curId.equals(getSelectedMethodIdLocked()) @@ -2988,6 +3050,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") @InputMethodNavButtonFlags private int getInputMethodNavButtonFlagsLocked() { + if (mImeDrawsImeNavBarResLazyInitFuture != null) { + // TODO(b/225366708): Avoid Future.get(), which is internally used here. + ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture, + "Waiting for the lazy init of mImeDrawsImeNavBarRes"); + } final boolean canImeDrawsImeNavBar = mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get(); final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked( @@ -3826,7 +3893,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case LayoutParams.SOFT_INPUT_STATE_VISIBLE: if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); - if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( + if (isSoftInputModeStateVisibleAllowed( unverifiedTargetSdkVersion, startInputFlags)) { if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, attribute, @@ -3844,7 +3911,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub break; case LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: if (DEBUG) Slog.v(TAG, "Window asks to always show input"); - if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( + if (isSoftInputModeStateVisibleAllowed( unverifiedTargetSdkVersion, startInputFlags)) { if (!sameWindowFocused) { if (attribute != null) { @@ -4430,7 +4497,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void finishStylusHandwriting(int requestId) { + private void resetStylusHandwriting(int requestId) { synchronized (ImfLock.class) { final OptionalInt curRequest = mHwController.getCurrentRequestId(); if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) { @@ -4797,6 +4864,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } return true; + case MSG_FINISH_HANDWRITING: + synchronized (ImfLock.class) { + IInputMethodInvoker curMethod = getCurMethodLocked(); + if (curMethod != null && mHwController.getCurrentRequestId().isPresent()) { + curMethod.finishStylusHandwriting(); + } + } + return true; } return false; } @@ -5435,6 +5510,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } } + + @Override + public void maybeFinishStylusHandwriting() { + mHandler.removeMessages(MSG_FINISH_HANDWRITING); + mHandler.obtainMessage(MSG_FINISH_HANDWRITING).sendToTarget(); + } } @BinderThread @@ -6388,8 +6469,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void finishStylusHandwriting(int requestId) { - mImms.finishStylusHandwriting(requestId); + public void resetStylusHandwriting(int requestId) { + mImms.resetStylusHandwriting(requestId); } } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 0a34eedd47cb..589b8f18b1a5 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -162,6 +162,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; @@ -4539,9 +4540,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } try { // update rules for all installed applications - final PackageManager pm = mContext.getPackageManager(); final List<UserInfo> users; - final List<ApplicationInfo> apps; Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-users"); try { @@ -4549,26 +4548,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } - Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-uids"); + Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "iterate-uids"); try { - apps = pm.getInstalledApplications( - PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + final PackageManagerInternal packageManagerInternal = LocalServices.getService( + PackageManagerInternal.class); + final int usersSize = users.size(); + for (int i = 0; i < usersSize; ++i) { + final int userId = users.get(i).id; + final SparseBooleanArray sharedAppIdsHandled = new SparseBooleanArray(); + packageManagerInternal.forEachInstalledPackage(androidPackage -> { + final int appId = androidPackage.getUid(); + if (androidPackage.getSharedUserId() != null) { + if (sharedAppIdsHandled.indexOfKey(appId) < 0) { + sharedAppIdsHandled.put(appId, true); + } else { + return; + } + } + final int uid = UserHandle.getUid(userId, appId); + consumer.accept(uid); + }, userId); + } } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } - - final int usersSize = users.size(); - final int appsSize = apps.size(); - for (int i = 0; i < usersSize; i++) { - final UserInfo user = users.get(i); - for (int j = 0; j < appsSize; j++) { - final ApplicationInfo app = apps.get(j); - final int uid = UserHandle.getUid(user.id, app.uid); - consumer.accept(uid); - } - } } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } @@ -5451,11 +5454,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (LOGV) Slog.v(TAG, "setMeteredNetworkDenylist " + uid + ": " + enable); try { mNetworkManager.setUidOnMeteredNetworkDenylist(uid, enable); - mLogger.meteredAllowlistChanged(uid, enable); + mLogger.meteredDenylistChanged(uid, enable); if (Process.isApplicationUid(uid)) { final int sdkSandboxUid = Process.toSdkSandboxUid(uid); mNetworkManager.setUidOnMeteredNetworkDenylist(sdkSandboxUid, enable); - mLogger.meteredAllowlistChanged(sdkSandboxUid, enable); + mLogger.meteredDenylistChanged(sdkSandboxUid, enable); } } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting denylist (" + enable + ") rules for " + uid, e); @@ -5468,11 +5471,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (LOGV) Slog.v(TAG, "setMeteredNetworkAllowlist " + uid + ": " + enable); try { mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, enable); - mLogger.meteredDenylistChanged(uid, enable); + mLogger.meteredAllowlistChanged(uid, enable); if (Process.isApplicationUid(uid)) { final int sdkSandboxUid = Process.toSdkSandboxUid(uid); mNetworkManager.setUidOnMeteredNetworkAllowlist(sdkSandboxUid, enable); - mLogger.meteredDenylistChanged(sdkSandboxUid, enable); + mLogger.meteredAllowlistChanged(sdkSandboxUid, enable); } } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting allowlist (" + enable + ") rules for " + uid, e); diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 76d3d233d49a..a91c55fb895d 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -520,7 +520,7 @@ public abstract class ApexManager { @Override public List<ActiveApexInfo> getActiveApexInfos() { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", - Trace.TRACE_TAG_APEX_MANAGER); + Trace.TRACE_TAG_PACKAGE_MANAGER); synchronized (mLock) { if (mActiveApexInfosCache == null) { t.traceBegin("getActiveApexInfos_noCache"); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 54a103959d04..4abfd3404295 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -1429,7 +1429,8 @@ public class ComputerEngine implements Computer { if (userId == UserHandle.USER_SYSTEM) { return resolveInfos; } - for (int i = resolveInfos.size() - 1; i >= 0; i--) { + + for (int i = CollectionUtils.size(resolveInfos) - 1; i >= 0; i--) { ResolveInfo info = resolveInfos.get(i); if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) { resolveInfos.remove(i); diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 45c5116bea59..fff66629048d 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -284,6 +284,17 @@ public class Installer extends SystemService { } /** + * Sets in Installd that it is first boot after data wipe + */ + public void setFirstBoot() throws InstallerException { + try { + mInstalld.setFirstBoot(); + } catch (RemoteException e) { + throw InstallerException.from(e); + } + } + + /** * Class that collects multiple {@code installd} operations together in an * attempt to more efficiently execute them in bulk. * <p> diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index aa5f39f78cbd..002d500e4df5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -640,7 +640,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements && params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH) ? params.installerPackageName : installerPackageName; - if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { + if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID) + || PackageInstallerSession.isSystemDataLoaderInstallation(params)) { params.installFlags |= PackageManager.INSTALL_FROM_ADB; // adb installs can override the installingPackageName, but not the // initiatingPackageName @@ -666,8 +667,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements && !mPm.isCallerVerifier(snapshot, callingUid)) { params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD; } - if (mContext.checkCallingOrSelfPermission( - Manifest.permission.INSTALL_TEST_ONLY_PACKAGE) + if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_TEST_ONLY_PACKAGE) != PackageManager.PERMISSION_GRANTED) { params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index beb198643e78..5ba4cc115c21 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -29,7 +29,6 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT; -import static android.content.pm.PackageManager.INSTALL_FROM_ADB; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_STAGED; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; @@ -705,6 +704,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } }; + static boolean isDataLoaderInstallation(SessionParams params) { + return params.dataLoaderParams != null; + } + + static boolean isSystemDataLoaderInstallation(SessionParams params) { + if (!isDataLoaderInstallation(params)) { + return false; + } + return SYSTEM_DATA_LOADER_PACKAGE.equals( + params.dataLoaderParams.getComponentName().getPackageName()); + } + private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { @@ -744,7 +755,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { }; private boolean isDataLoaderInstallation() { - return params.dataLoaderParams != null; + return isDataLoaderInstallation(this.params); } private boolean isStreamingInstallation() { @@ -756,11 +767,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private boolean isSystemDataLoaderInstallation() { - if (!isDataLoaderInstallation()) { - return false; - } - return SYSTEM_DATA_LOADER_PACKAGE.equals( - this.params.dataLoaderParams.getComponentName().getPackageName()); + return isSystemDataLoaderInstallation(this.params); } /** @@ -957,17 +964,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "DataLoader installation of APEX modules is not allowed."); } - if (isSystemDataLoaderInstallation()) { - if (mContext.checkCallingOrSelfPermission( - Manifest.permission.USE_SYSTEM_DATA_LOADERS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("You need the " - + "com.android.permission.USE_SYSTEM_DATA_LOADERS permission " - + "to use system data loaders"); - } - - // All installations using system dataloaders marked as ADB. - this.params.installFlags |= INSTALL_FROM_ADB; + if (isSystemDataLoaderInstallation() && mContext.checkCallingOrSelfPermission( + Manifest.permission.USE_SYSTEM_DATA_LOADERS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the " + + "com.android.permission.USE_SYSTEM_DATA_LOADERS permission " + + "to use system data loaders"); } } @@ -1264,13 +1266,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } - final String initiatingPackageName = getInstallSource().initiatingPackageName; + final String installerPackageName; + if (!TextUtils.isEmpty(getInstallSource().initiatingPackageName)) { + installerPackageName = getInstallSource().initiatingPackageName; + } else { + installerPackageName = getInstallSource().installerPackageName; + } + if (TextUtils.isEmpty(installerPackageName)) { + throw new IllegalStateException("Installer package is empty."); + } final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); - appOps.checkPackage(Binder.getCallingUid(), initiatingPackageName); + appOps.checkPackage(Binder.getCallingUid(), installerPackageName); final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); - final AndroidPackage callingInstaller = pmi.getPackage(initiatingPackageName); + final AndroidPackage callingInstaller = pmi.getPackage(installerPackageName); if (callingInstaller == null) { throw new IllegalStateException("Can't obtain calling installer's package."); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b1b05bedfcad..4c7243decb07 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1254,9 +1254,18 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (applicationInfo == null) { throw new ParcelableException(new PackageManager.NameNotFoundException(packageName)); } + final InstallSourceInfo installSourceInfo = snapshot.getInstallSourceInfo(packageName); - final String installerPackageName = - installSourceInfo != null ? installSourceInfo.getInitiatingPackageName() : null; + final String installerPackageName; + if (installSourceInfo != null) { + if (!TextUtils.isEmpty(installSourceInfo.getInitiatingPackageName())) { + installerPackageName = installSourceInfo.getInitiatingPackageName(); + } else { + installerPackageName = installSourceInfo.getInstallingPackageName(); + } + } else { + installerPackageName = null; + } List<Pair<String, File>> filesToChecksum = new ArrayList<>(); @@ -1891,6 +1900,16 @@ public class PackageManagerService implements PackageSender, TestUtilityService /* excludePreCreated= */ false)); t.traceEnd(); + if (mFirstBoot) { + t.traceBegin("setFirstBoot: "); + try { + mInstaller.setFirstBoot(); + } catch (InstallerException e) { + Slog.w(TAG, "Could not set First Boot: ", e); + } + t.traceEnd(); + } + mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions); mPermissionManager.readLegacyPermissionStateTEMP(); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index c8f809b6782f..7a73412c9b9f 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -19,9 +19,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Person; +import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSession; +import android.app.appsearch.BatchResultCallback; +import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByDocumentIdRequest; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.PutDocumentsRequest; @@ -2384,14 +2387,24 @@ class ShortcutPackage extends ShortcutPackageItem { } runAsSystem(() -> fromAppSearch().thenAccept(session -> { session.getByDocumentId(new GetByDocumentIdRequest.Builder(getPackageName()) - .addIds(ids).build(), mShortcutUser.mExecutor, result -> { - final List<ShortcutInfo> ret = result.getSuccesses().values() - .stream().map(doc -> - ShortcutInfo.createFromGenericDocument( - mShortcutUser.getUserId(), doc)) - .collect(Collectors.toList()); - cb.accept(ret); - }); + .addIds(ids).build(), mShortcutUser.mExecutor, + new BatchResultCallback<String, GenericDocument>() { + @Override + public void onResult( + @NonNull AppSearchBatchResult<String, GenericDocument> result) { + final List<ShortcutInfo> ret = result.getSuccesses().values() + .stream().map(doc -> + ShortcutInfo.createFromGenericDocument( + mShortcutUser.getUserId(), doc)) + .collect(Collectors.toList()); + cb.accept(ret); + } + @Override + public void onSystemError( + @Nullable Throwable throwable) { + Slog.d(TAG, "Error retrieving shortcuts", throwable); + } + }); })); } @@ -2407,15 +2420,24 @@ class ShortcutPackage extends ShortcutPackageItem { runAsSystem(() -> fromAppSearch().thenAccept(session -> session.remove( new RemoveByDocumentIdRequest.Builder(getPackageName()).addIds(ids).build(), - mShortcutUser.mExecutor, result -> { - if (!result.isSuccess()) { - final Map<String, AppSearchResult<Void>> failures = - result.getFailures(); - for (String key : failures.keySet()) { - Slog.e(TAG, "Failed deleting " + key + ", error message:" - + failures.get(key).getErrorMessage()); + mShortcutUser.mExecutor, + new BatchResultCallback<String, Void>() { + @Override + public void onResult( + @NonNull AppSearchBatchResult<String, Void> result) { + if (!result.isSuccess()) { + final Map<String, AppSearchResult<Void>> failures = + result.getFailures(); + for (String key : failures.keySet()) { + Slog.e(TAG, "Failed deleting " + key + ", error message:" + + failures.get(key).getErrorMessage()); + } } } + @Override + public void onSystemError(@Nullable Throwable throwable) { + Slog.e(TAG, "Error removing shortcuts", throwable); + } }))); } @@ -2452,12 +2474,20 @@ class ShortcutPackage extends ShortcutPackageItem { AppSearchShortcutInfo.toGenericDocuments(shortcuts)) .build(), mShortcutUser.mExecutor, - result -> { - if (!result.isSuccess()) { - for (AppSearchResult<Void> k : result.getFailures().values()) { - Slog.e(TAG, k.getErrorMessage()); + new BatchResultCallback<String, Void>() { + @Override + public void onResult( + @NonNull AppSearchBatchResult<String, Void> result) { + if (!result.isSuccess()) { + for (AppSearchResult<Void> k : result.getFailures().values()) { + Slog.e(TAG, k.getErrorMessage()); + } } } + @Override + public void onSystemError(@Nullable Throwable throwable) { + Slog.d(TAG, "Error persisting shortcuts", throwable); + } }); })); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 70053bdeb47e..d340561c2862 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1387,7 +1387,7 @@ public class UserManagerService extends IUserManager.Stub { */ @Override public boolean isUserOfType(@UserIdInt int userId, String userType) { - checkManageUsersPermission("check user type"); + checkQueryOrCreateUsersPermission("check user type"); return userType != null && userType.equals(getUserTypeNoChecks(userId)); } @@ -1643,7 +1643,7 @@ public class UserManagerService extends IUserManager.Stub { if (!hasQueryOrCreateUsersPermission() && !hasPermissionGranted( android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid)) { - throw new SecurityException("You need MANAGE_USERS or CREATE_USERS or " + throw new SecurityException("You need MANAGE_USERS, CREATE_USERS, QUERY_USERS, or " + "GET_ACCOUNTS_PRIVILEGED permissions to: get user name"); } final int userId = UserHandle.getUserId(callingUid); @@ -5064,9 +5064,13 @@ public class UserManagerService extends IUserManager.Stub { @Override public boolean isUserNameSet(@UserIdInt int userId) { - if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) { - throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED " - + "permissions to: get whether user name is set"); + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + if (!hasQueryOrCreateUsersPermission() + && !(callingUserId == userId && hasPermissionGranted( + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid))) { + throw new SecurityException("You need MANAGE_USERS, CREATE_USERS, QUERY_USERS, or " + + "GET_ACCOUNTS_PRIVILEGED permissions to: get whether user name is set"); } synchronized (mUsersLock) { final UserInfo userInfo = getUserInfoLU(userId); @@ -5424,32 +5428,62 @@ public class UserManagerService extends IUserManager.Stub { private static final String ARG_CRITICAL_ONLY = "--critical-only"; private static final String ARG_MODE = "--mode"; - private int onShellCommand(Shell shell, String cmd) { + private final class Shell extends ShellCommand { + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.printf("User manager (user) commands:\n"); + + pw.printf("%s%s\n", PREFIX_HELP_COMMAND, CMD_HELP); + pw.printf("%sPrints this help text.\n\n", PREFIX_HELP_DESCRIPTION); + + pw.printf("%s%s [%s] [%s]\n", PREFIX_HELP_COMMAND, CMD_LIST, ARG_V, ARG_ALL); + pw.printf("%sPrints all users on the system.\n\n", PREFIX_HELP_DESCRIPTION); + + pw.printf("%s%s [%s | %s] [%s] [%s MODE]\n", PREFIX_HELP_COMMAND, + CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS, + ARG_V, ARG_VERBOSE, ARG_CRITICAL_ONLY, ARG_MODE); + + pw.printf("%sReports all issues on user-type package allowlist XML files. Options:\n", + PREFIX_HELP_DESCRIPTION); + pw.printf("%s%s | %s: shows extra info, like number of issues\n", + PREFIX_HELP_DESCRIPTION, ARG_V, ARG_VERBOSE); + pw.printf("%s%s: show only critical issues, excluding warnings\n", + PREFIX_HELP_DESCRIPTION, ARG_CRITICAL_ONLY); + pw.printf("%s%s MODE: shows what errors would be if device used mode MODE\n" + + "%s(where MODE is the allowlist mode integer as defined by " + + "config_userTypePackageWhitelistMode)\n\n", + PREFIX_HELP_DESCRIPTION, ARG_MODE, PREFIX_HELP_DESCRIPTION_EXTRA_LINES); + } + + @Override + public int onCommand(String cmd) { if (cmd == null) { - return shell.handleDefaultCommands(cmd); + return handleDefaultCommands(cmd); } - final PrintWriter pw = shell.getOutPrintWriter(); try { switch(cmd) { case CMD_LIST: - return runList(pw, shell); + return runList(); case CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS: - return runReportPackageWhitelistProblems(pw, shell); + return runReportPackageAllowlistProblems(); default: - return shell.handleDefaultCommands(cmd); + return handleDefaultCommands(cmd); } } catch (RemoteException e) { - pw.println("Remote exception: " + e); + getOutPrintWriter().println("Remote exception: " + e); } return -1; } - private int runList(PrintWriter pw, Shell shell) throws RemoteException { + private int runList() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); boolean all = false; boolean verbose = false; String opt; - while ((opt = shell.getNextOption()) != null) { + while ((opt = getNextOption()) != null) { switch (opt) { case ARG_V: verbose = true; @@ -5528,12 +5562,13 @@ public class UserManagerService extends IUserManager.Stub { } } - private int runReportPackageWhitelistProblems(PrintWriter pw, Shell shell) { + private int runReportPackageAllowlistProblems() { + final PrintWriter pw = getOutPrintWriter(); boolean verbose = false; boolean criticalOnly = false; int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE; String opt; - while ((opt = shell.getNextOption()) != null) { + while ((opt = getNextOption()) != null) { switch (opt) { case ARG_V: case ARG_VERBOSE: @@ -5543,7 +5578,7 @@ public class UserManagerService extends IUserManager.Stub { criticalOnly = true; break; case ARG_MODE: - mode = Integer.parseInt(shell.getNextArgRequired()); + mode = Integer.parseInt(getNextArgRequired()); break; default: pw.println("Invalid option: " + opt); @@ -5551,15 +5586,17 @@ public class UserManagerService extends IUserManager.Stub { } } - Slog.d(LOG_TAG, "runReportPackageWhitelistProblems(): verbose=" + verbose + Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose + ", criticalOnly=" + criticalOnly + ", mode=" + UserSystemPackageInstaller.modeToString(mode)); try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { - mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose, criticalOnly); + mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose, + criticalOnly); } return 0; } + } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -6254,40 +6291,6 @@ public class UserManagerService extends IUserManager.Stub { } } - private class Shell extends ShellCommand { - @Override - public int onCommand(String cmd) { - return onShellCommand(this, cmd); - } - - @Override - public void onHelp() { - final PrintWriter pw = getOutPrintWriter(); - pw.printf("User manager (user) commands:\n"); - - pw.printf("%s%s\n", PREFIX_HELP_COMMAND, CMD_HELP); - pw.printf("%sPrints this help text.\n\n", PREFIX_HELP_DESCRIPTION); - - pw.printf("%s%s [%s] [%s]\n", PREFIX_HELP_COMMAND, CMD_LIST, ARG_V, ARG_ALL); - pw.printf("%sPrints all users on the system.\n\n", PREFIX_HELP_DESCRIPTION); - - pw.printf("%s%s [%s | %s] [%s] [%s MODE]\n", PREFIX_HELP_COMMAND, - CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS, - ARG_V, ARG_VERBOSE, ARG_CRITICAL_ONLY, ARG_MODE); - - pw.printf("%sReports all issues on user-type package allowlist XML files. Options:\n", - PREFIX_HELP_DESCRIPTION); - pw.printf("%s%s | %s: shows extra info, like number of issues\n", - PREFIX_HELP_DESCRIPTION, ARG_V, ARG_VERBOSE); - pw.printf("%s%s: show only critical issues, excluding warnings\n", - PREFIX_HELP_DESCRIPTION, ARG_CRITICAL_ONLY); - pw.printf("%s%s MODE: shows what errors would be if device used mode MODE\n" - + "%s(where MODE is the allowlist mode integer as defined by " - + "config_userTypePackageWhitelistMode)\n\n", - PREFIX_HELP_DESCRIPTION, ARG_MODE, PREFIX_HELP_DESCRIPTION_EXTRA_LINES); - } - } - private static void debug(String message) { Slog.d(LOG_TAG, message + (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : "")); diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 60602337ba1a..881f8707fdd8 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -317,9 +317,21 @@ public class OneTimePermissionUserManager { synchronized (mInnerLock) { mIsFinished = true; cancelAlarmLocked(); - mActivityManager.removeOnUidImportanceListener(mStartTimerListener); - mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); - mActivityManager.removeOnUidImportanceListener(mGoneListener); + try { + mActivityManager.removeOnUidImportanceListener(mStartTimerListener); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "Could not remove start timer listener", e); + } + try { + mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "Could not remove session killable listener", e); + } + try { + mActivityManager.removeOnUidImportanceListener(mGoneListener); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "Could not remove gone listener", e); + } } } diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 70ef3d364277..bd9e8923e984 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -153,9 +153,10 @@ public final class PermissionPolicyService extends SystemService { private List<String> mAppOpPermissions; - private Context mContext; - private Handler mHandler; + private final Context mContext; + private final Handler mHandler; private PackageManagerInternal mPackageManagerInternal; + private PermissionManagerServiceInternal mPermissionManagerInternal; private NotificationManagerInternal mNotificationManager; private final KeyguardManager mKeyguardManager; private final PackageManager mPackageManager; @@ -174,7 +175,7 @@ public final class PermissionPolicyService extends SystemService { public void onStart() { mPackageManagerInternal = LocalServices.getService( PackageManagerInternal.class); - PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService( + mPermissionManagerInternal = LocalServices.getService( PermissionManagerServiceInternal.class); final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); @@ -206,7 +207,7 @@ public final class PermissionPolicyService extends SystemService { } }); - permissionManagerInternal.addOnRuntimePermissionStateChangedListener( + mPermissionManagerInternal.addOnRuntimePermissionStateChangedListener( this::synchronizePackagePermissionsAndAppOpsAsyncForUser); mAppOpsCallback = new IAppOpsCallback.Stub() { @@ -218,7 +219,7 @@ public final class PermissionPolicyService extends SystemService { }; final ArrayList<PermissionInfo> dangerousPerms = - permissionManagerInternal.getAllPermissionsWithProtection( + mPermissionManagerInternal.getAllPermissionsWithProtection( PermissionInfo.PROTECTION_DANGEROUS); try { int numDangerousPerms = dangerousPerms.size(); @@ -243,7 +244,7 @@ public final class PermissionPolicyService extends SystemService { } final List<PermissionInfo> appOpPermissionInfos = - permissionManagerInternal.getAllPermissionsWithProtectionFlags( + mPermissionManagerInternal.getAllPermissionsWithProtectionFlags( PermissionInfo.PROTECTION_FLAG_APPOP); mAppOpPermissions = new ArrayList<>(); final int appOpPermissionInfosSize = appOpPermissionInfos.size(); @@ -1283,10 +1284,12 @@ public final class PermissionPolicyService extends SystemService { } boolean hasCreatedNotificationChannels = mNotificationManager .getNumNotificationChannelsForPackage(pkgName, uid, true) > 0; + boolean granted = mPermissionManagerInternal.checkUidPermission(uid, POST_NOTIFICATIONS) + == PackageManager.PERMISSION_GRANTED; int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user); boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0; boolean needsReview = (flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0; - return hasCreatedNotificationChannels && (needsReview || !explicitlySet); + return !granted && hasCreatedNotificationChannels && (needsReview || !explicitlySet); } } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1ac373f074ec..5aa81ac9086d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5446,8 +5446,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { VibrationAttributes attrs = getVibrationAttributes(effectId); if (always) { attrs = new VibrationAttributes.Builder(attrs) - .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF, - VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) .build(); } mVibrator.vibrate(uid, packageName, effect, reason, attrs); diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index adca21676f9d..d3748140a5a5 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -40,12 +40,16 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.service.trust.GrantTrustResult; import android.service.trust.ITrustAgentService; import android.service.trust.ITrustAgentServiceCallback; import android.service.trust.TrustAgentService; import android.util.Log; +import android.util.Pair; import android.util.Slog; +import com.android.internal.infra.AndroidFuture; + import java.util.Collections; import java.util.List; @@ -156,7 +160,9 @@ public class TrustAgentWrapper { } mTrusted = true; mTrustable = false; - mMessage = (CharSequence) msg.obj; + Pair<CharSequence, AndroidFuture<GrantTrustResult>> pair = (Pair) msg.obj; + mMessage = pair.first; + AndroidFuture<GrantTrustResult> resultCallback = pair.second; int flags = msg.arg1; mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0; if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) { @@ -189,7 +195,7 @@ public class TrustAgentWrapper { mTrustManagerService.mArchive.logGrantTrust(mUserId, mName, (mMessage != null ? mMessage.toString() : null), durationMs, flags); - mTrustManagerService.updateTrust(mUserId, flags); + mTrustManagerService.updateTrust(mUserId, flags, resultCallback); break; case MSG_TRUST_TIMEOUT: if (DEBUG) Slog.d(TAG, "Trust timed out : " + mName.flattenToShortString()); @@ -314,13 +320,18 @@ public class TrustAgentWrapper { private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() { @Override - public void grantTrust(CharSequence message, long durationMs, int flags) { + public void grantTrust( + CharSequence message, + long durationMs, + int flags, + AndroidFuture resultCallback) { if (DEBUG) { Slog.d(TAG, "enableTrust(" + message + ", durationMs = " + durationMs + ", flags = " + flags + ")"); } - Message msg = mHandler.obtainMessage(MSG_GRANT_TRUST, flags, 0, message); + Message msg = mHandler.obtainMessage( + MSG_GRANT_TRUST, flags, 0, Pair.create(message, resultCallback)); msg.getData().putLong(DATA_DURATION, durationMs); msg.sendToTarget(); } @@ -514,12 +525,23 @@ public class TrustAgentWrapper { } /** - * @see android.service.trust.TrustAgentService#onUserRequestedUnlock() + * @see android.service.trust.TrustAgentService#onUserRequestedUnlock(boolean) */ - public void onUserRequestedUnlock() { + public void onUserRequestedUnlock(boolean dismissKeyguard) { + try { + if (mTrustAgentService != null) { + mTrustAgentService.onUserRequestedUnlock(dismissKeyguard); + } + } catch (RemoteException e) { + onError(e); + } + } + + /** @see android.service.trust.TrustAgentService#onUserMayRequestUnlock() */ + public void onUserMayRequestUnlock() { try { if (mTrustAgentService != null) { - mTrustAgentService.onUserRequestedUnlock(); + mTrustAgentService.onUserMayRequestUnlock(); } } catch (RemoteException e) { onError(e); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index cc1d0e2ea6c8..a486364518b9 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -61,6 +61,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Authorization; +import android.service.trust.GrantTrustResult; import android.service.trust.TrustAgentService; import android.text.TextUtils; import android.util.ArrayMap; @@ -77,6 +78,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; @@ -131,6 +133,7 @@ public class TrustManagerService extends SystemService { private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15; private static final int MSG_USER_REQUESTED_UNLOCK = 16; private static final int MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH = 17; + private static final int MSG_USER_MAY_REQUEST_UNLOCK = 18; private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except"; @@ -495,13 +498,28 @@ public class TrustManagerService extends SystemService { } } - public void updateTrust(int userId, int flags) { - updateTrust(userId, flags, false /* isFromUnlock */); + /** Triggers a trust update. */ + public void updateTrust( + int userId, + int flags) { + updateTrust(userId, flags, null); } - private void updateTrust(int userId, int flags, boolean isFromUnlock) { + /** Triggers a trust update. */ + public void updateTrust( + int userId, + int flags, + @Nullable AndroidFuture<GrantTrustResult> resultCallback) { + updateTrust(userId, flags, false /* isFromUnlock */, resultCallback); + } + + private void updateTrust( + int userId, + int flags, + boolean isFromUnlock, + @Nullable AndroidFuture<GrantTrustResult> resultCallback) { if (ENABLE_ACTIVE_UNLOCK_FLAG) { - updateTrustWithRenewableUnlock(userId, flags, isFromUnlock); + updateTrustWithRenewableUnlock(userId, flags, isFromUnlock, resultCallback); } else { updateTrustWithNonrenewableTrust(userId, flags, isFromUnlock); } @@ -553,7 +571,11 @@ public class TrustManagerService extends SystemService { } } - private void updateTrustWithRenewableUnlock(int userId, int flags, boolean isFromUnlock) { + private void updateTrustWithRenewableUnlock( + int userId, + int flags, + boolean isFromUnlock, + @Nullable AndroidFuture<GrantTrustResult> resultCallback) { boolean managed = aggregateIsTrustManaged(userId); dispatchOnTrustManagedChanged(managed, userId); if (mStrongAuthTracker.isTrustAllowedForUser(userId) @@ -614,8 +636,18 @@ public class TrustManagerService extends SystemService { isTrustableTimeout /* isTrustableTimeout */); } } - } + boolean wasLocked = !alreadyUnlocked; + boolean shouldSendCallback = wasLocked && pendingTrustState == TrustState.TRUSTED; + if (shouldSendCallback) { + if (resultCallback != null) { + if (DEBUG) Slog.d(TAG, "calling back with UNLOCKED_BY_GRANT"); + resultCallback.complete( + GrantTrustResult.withStatus( + GrantTrustResult.STATUS_UNLOCKED_BY_GRANT)); + } + } + } private void updateTrustUsuallyManaged(int userId, boolean managed) { synchronized (mTrustUsuallyManagedForUser) { @@ -1190,7 +1222,7 @@ public class TrustManagerService extends SystemService { if (successful) { mStrongAuthTracker.allowTrustFromUnlock(userId); // Allow the presence of trust on a successful unlock attempt to extend unlock - updateTrust(userId, 0 /* flags */, true); + updateTrust(userId, 0 /* flags */, true, null); mHandler.obtainMessage(MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH, userId).sendToTarget(); } @@ -1202,11 +1234,27 @@ public class TrustManagerService extends SystemService { } } - private void dispatchUserRequestedUnlock(int userId) { + private void dispatchUserRequestedUnlock(int userId, boolean dismissKeyguard) { + if (DEBUG) { + Slog.d(TAG, "dispatchUserRequestedUnlock(user=" + userId + ", dismissKeyguard=" + + dismissKeyguard + ")"); + } for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); if (info.userId == userId) { - info.agent.onUserRequestedUnlock(); + info.agent.onUserRequestedUnlock(dismissKeyguard); + } + } + } + + private void dispatchUserMayRequestUnlock(int userId) { + if (DEBUG) { + Slog.d(TAG, "dispatchUserMayRequestUnlock(user=" + userId + ")"); + } + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == userId) { + info.agent.onUserMayRequestUnlock(); } } } @@ -1341,9 +1389,17 @@ public class TrustManagerService extends SystemService { } @Override - public void reportUserRequestedUnlock(int userId) throws RemoteException { + public void reportUserRequestedUnlock(int userId, boolean dismissKeyguard) + throws RemoteException { enforceReportPermission(); - mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId).sendToTarget(); + mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId, dismissKeyguard ? 1 : 0) + .sendToTarget(); + } + + @Override + public void reportUserMayRequestUnlock(int userId) throws RemoteException { + enforceReportPermission(); + mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId).sendToTarget(); } @Override @@ -1683,7 +1739,10 @@ public class TrustManagerService extends SystemService { dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2); break; case MSG_USER_REQUESTED_UNLOCK: - dispatchUserRequestedUnlock(msg.arg1); + dispatchUserRequestedUnlock(msg.arg1, msg.arg2 != 0); + break; + case MSG_USER_MAY_REQUEST_UNLOCK: + dispatchUserMayRequestUnlock(msg.arg1); break; case MSG_DISPATCH_UNLOCK_LOCKOUT: dispatchUnlockLockout(msg.arg1, msg.arg2); @@ -1725,7 +1784,7 @@ public class TrustManagerService extends SystemService { break; case MSG_REFRESH_DEVICE_LOCKED_FOR_USER: if (msg.arg2 == 1) { - updateTrust(msg.arg1, 0 /* flags */, true /* isFromUnlock */); + updateTrust(msg.arg1, 0 /* flags */, true /* isFromUnlock */, null); } final int unlockedUser = msg.getData().getInt( REFRESH_DEVICE_LOCKED_EXCEPT_USER, UserHandle.USER_NULL); diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index f02f9f98d933..8ecc51b3087c 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -47,12 +47,16 @@ final class Vibration { FINISHED, FINISHED_UNEXPECTED, // Didn't terminate in the usual way. FORWARDED_TO_INPUT_DEVICES, - CANCELLED, + CANCELLED_BINDER_DIED, + CANCELLED_BY_SCREEN_OFF, + CANCELLED_BY_SETTINGS_UPDATE, + CANCELLED_BY_USER, + CANCELLED_BY_UNKNOWN_REASON, + CANCELLED_SUPERSEDED, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING, IGNORED_ERROR_TOKEN, - IGNORED, IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_UNKNOWN_VIBRATION, diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 77da75118958..e9535e0a56e1 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -121,6 +121,11 @@ final class VibrationSettings { USAGE_PHYSICAL_EMULATION, USAGE_HARDWARE_FEEDBACK)); + private static final IntentFilter USER_SWITCHED_INTENT_FILTER = + new IntentFilter(Intent.ACTION_USER_SWITCHED); + private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER = + new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); + /** Listener for changes on vibration settings. */ interface OnVibratorSettingsChanged { /** Callback triggered when any of the vibrator settings change. */ @@ -130,11 +135,11 @@ final class VibrationSettings { private final Object mLock = new Object(); private final Context mContext; private final String mSystemUiPackage; - private final SettingsObserver mSettingObserver; + private final SettingsContentObserver mSettingObserver; @VisibleForTesting final UidObserver mUidObserver; @VisibleForTesting - final UserObserver mUserReceiver; + final SettingsBroadcastReceiver mSettingChangeReceiver; @GuardedBy("mLock") private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>(); @@ -154,6 +159,8 @@ final class VibrationSettings { private boolean mBatterySaverMode; @GuardedBy("mLock") private boolean mVibrateOn; + @GuardedBy("mLock") + private int mRingerMode; VibrationSettings(Context context, Handler handler) { this(context, handler, new VibrationConfig(context.getResources())); @@ -163,9 +170,9 @@ final class VibrationSettings { VibrationSettings(Context context, Handler handler, VibrationConfig config) { mContext = context; mVibrationConfig = config; - mSettingObserver = new SettingsObserver(handler); + mSettingObserver = new SettingsContentObserver(handler); mUidObserver = new UidObserver(); - mUserReceiver = new UserObserver(); + mSettingChangeReceiver = new SettingsBroadcastReceiver(); mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) .getSystemUiServiceComponent().getPackageName(); @@ -188,12 +195,13 @@ final class VibrationSettings { VibrationEffect.get(VibrationEffect.EFFECT_TICK, false)); // Update with current values from settings. - updateSettings(); + update(); } public void onSystemReady() { synchronized (mLock) { mAudioManager = mContext.getSystemService(AudioManager.class); + mRingerMode = mAudioManager.getRingerModeInternal(); } try { ActivityManager.getService().registerUidObserver(mUidObserver, @@ -224,8 +232,8 @@ final class VibrationSettings { } }); - IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mContext.registerReceiver(mUserReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER); + registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER); // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity. registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); @@ -248,7 +256,7 @@ final class VibrationSettings { Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY)); // Update with newly loaded services. - updateSettings(); + update(); } /** @@ -396,16 +404,17 @@ final class VibrationSettings { // Only ringtone and notification vibrations are disabled when phone is on silent mode. return true; } - // If audio manager was not loaded yet then assume most restrictive mode. - int ringerMode = (mAudioManager == null) - ? AudioManager.RINGER_MODE_SILENT - : mAudioManager.getRingerModeInternal(); - return ringerMode != AudioManager.RINGER_MODE_SILENT; + return mRingerMode != AudioManager.RINGER_MODE_SILENT; } - /** Updates all vibration settings and triggers registered listeners. */ - @VisibleForTesting - void updateSettings() { + /** Update all cached settings and triggers registered listeners. */ + void update() { + updateSettings(); + updateRingerMode(); + notifyListeners(); + } + + private void updateSettings() { synchronized (mLock) { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0; @@ -435,7 +444,6 @@ final class VibrationSettings { loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1), getDefaultIntensity(USAGE_RINGTONE)); - mCurrentVibrationIntensities.clear(); mCurrentVibrationIntensities.put(USAGE_ALARM, alarmIntensity); mCurrentVibrationIntensities.put(USAGE_NOTIFICATION, notificationIntensity); @@ -469,7 +477,16 @@ final class VibrationSettings { // A11y is not disabled by any haptic feedback setting. mCurrentVibrationIntensities.put(USAGE_ACCESSIBILITY, positiveHapticFeedbackIntensity); } - notifyListeners(); + } + + private void updateRingerMode() { + synchronized (mLock) { + // If audio manager was not loaded yet then assume most restrictive mode. + // This will be loaded again as soon as the audio manager is loaded in onSystemReady. + mRingerMode = (mAudioManager == null) + ? AudioManager.RINGER_MODE_SILENT + : mAudioManager.getRingerModeInternal(); + } } @Override @@ -586,6 +603,11 @@ final class VibrationSettings { UserHandle.USER_ALL); } + private void registerSettingsChangeReceiver(IntentFilter intentFilter) { + mContext.registerReceiver(mSettingChangeReceiver, intentFilter, + Context.RECEIVER_NOT_EXPORTED); + } + @Nullable private VibrationEffect createEffectFromResource(int resId) { long[] timings = getLongIntArray(mContext.getResources(), resId); @@ -616,24 +638,33 @@ final class VibrationSettings { } /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */ - private final class SettingsObserver extends ContentObserver { - SettingsObserver(Handler handler) { + private final class SettingsContentObserver extends ContentObserver { + SettingsContentObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { updateSettings(); + notifyListeners(); } } - /** Implementation of {@link BroadcastReceiver} to update settings on current user change. */ + /** + * Implementation of {@link BroadcastReceiver} to update settings on current user or ringer + * mode change. + */ @VisibleForTesting - final class UserObserver extends BroadcastReceiver { + final class SettingsBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { - updateSettings(); + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + // Reload all settings, as they are user-based. + update(); + } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { + updateRingerMode(); + notifyListeners(); } } } diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index e12426b2b02c..e3d806755c6e 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -79,12 +79,14 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { private final Object mLock = new Object(); @GuardedBy("mLock") private final IntArray mSignalVibratorsComplete; + @Nullable @GuardedBy("mLock") - private boolean mSignalCancel = false; + private Vibration.Status mSignalCancelStatus = null; @GuardedBy("mLock") private boolean mSignalCancelImmediate = false; - private boolean mCancelled = false; + @Nullable + private Vibration.Status mCancelStatus = null; private boolean mCancelledImmediately = false; // hard stop private int mPendingVibrateSteps; private int mRemainingStartSequentialEffectSteps; @@ -185,8 +187,8 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { expectIsVibrationThread(true); } - if (mCancelled) { - return Vibration.Status.CANCELLED; + if (mCancelStatus != null) { + return mCancelStatus; } if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) { @@ -303,7 +305,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { if (DEBUG) { Slog.d(TAG, "Binder died, cancelling vibration..."); } - notifyCancelled(/* immediate= */ false); + notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false); } /** @@ -312,22 +314,41 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps. */ - public void notifyCancelled(boolean immediate) { + public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(false); } + if (DEBUG) { + Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus + + ", immediate=" + immediate); + } + if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) { + Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus + + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation."); + cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON; + } synchronized (mLock) { - if (immediate && mSignalCancelImmediate || mSignalCancel) { - // Nothing to update: already cancelled previously. + if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) { + if (DEBUG) { + Slog.d(TAG, "Vibration cancel request ignored as the vibration " + + mVibration.id + "is already being cancelled with status=" + + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate); + } return; } mSignalCancelImmediate |= immediate; - mSignalCancel = true; + if (mSignalCancelStatus == null) { + mSignalCancelStatus = cancelStatus; + } else { + if (DEBUG) { + Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus + + " ignored as the vibration was already cancelled with status=" + + mSignalCancelStatus + ", but immediate flag was updated to " + + mSignalCancelImmediate); + } + } mLock.notify(); } - if (DEBUG) { - Slog.d(TAG, "Vibration cancel requested, immediate=" + immediate); - } } /** @@ -380,7 +401,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); // Reads VibrationThread variables as well as signals. } - return (mSignalCancel && !mCancelled) + return (mSignalCancelStatus != mCancelStatus) || (mSignalCancelImmediate && !mCancelledImmediately) || (mSignalVibratorsComplete.size() > 0); } @@ -395,7 +416,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } int[] vibratorsToProcess = null; - boolean doCancel = false; + Vibration.Status doCancelStatus = null; boolean doCancelImmediate = false; // Collect signals to process, but don't keep the lock while processing them. synchronized (mLock) { @@ -405,9 +426,10 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } // This should only happen once. doCancelImmediate = true; + doCancelStatus = mSignalCancelStatus; } - if (mSignalCancel && !mCancelled) { - doCancel = true; + if (mSignalCancelStatus != mCancelStatus) { + doCancelStatus = mSignalCancelStatus; } if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) { // Swap out the queue of completions to process. @@ -421,11 +443,11 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { // completion signals that were collected in this call, but we won't process them // anyway as all steps are cancelled. if (doCancelImmediate) { - processCancelImmediately(); + processCancelImmediately(doCancelStatus); return; } - if (doCancel) { - processCancel(); + if (doCancelStatus != null) { + processCancel(doCancelStatus); } if (vibratorsToProcess != null) { processVibratorsComplete(vibratorsToProcess); @@ -438,12 +460,12 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * <p>This will remove all steps and replace them with respective results of * {@link Step#cancel()}. */ - public void processCancel() { + public void processCancel(Vibration.Status cancelStatus) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); } - mCancelled = true; + mCancelStatus = cancelStatus; // Vibrator callbacks should wait until all steps from the queue are properly cancelled // and clean up steps are added back to the queue, so they can handle the callback. List<Step> cleanUpSteps = new ArrayList<>(); @@ -461,13 +483,13 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order. */ - public void processCancelImmediately() { + public void processCancelImmediately(Vibration.Status cancelStatus) { if (Build.IS_DEBUGGABLE) { expectIsVibrationThread(true); } mCancelledImmediately = true; - mCancelled = true; + mCancelStatus = cancelStatus; Step step; while ((step = pollNext()) != null) { step.cancelImmediately(); diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 1260e5dbc9f7..f749773d14a0 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -162,10 +162,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // When the system is entering a non-interactive state, we want to cancel // vibrations in case a misbehaving app has abandoned them. if (shouldCancelOnScreenOffLocked(mNextVibration)) { - clearNextVibrationLocked(Vibration.Status.CANCELLED); + clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF); } if (shouldCancelOnScreenOffLocked(mCurrentVibration)) { - mCurrentVibration.notifyCancelled(/* immediate= */ false); + mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF, + /* immediate= */ false); } } } @@ -401,6 +402,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { uid, opPkg, reason); fillVibrationFallbacks(vib, effect); + if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { + // Force update of user settings before checking if this vibration effect should + // be ignored or scaled. + mVibrationSettings.update(); + } + synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "Starting vibrate for vibration " + vib.id); @@ -420,7 +427,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { final long ident = Binder.clearCallingIdentity(); try { if (mCurrentVibration != null) { - mCurrentVibration.notifyCancelled(/* immediate= */ false); + mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, + /* immediate= */ false); } Vibration.Status status = startVibrationLocked(vib); if (status != Vibration.Status.RUNNING) { @@ -453,19 +461,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (mNextVibration != null && shouldCancelVibration(mNextVibration.getVibration(), usageFilter, token)) { - clearNextVibrationLocked(Vibration.Status.CANCELLED); + clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER); } if (mCurrentVibration != null && shouldCancelVibration(mCurrentVibration.getVibration(), usageFilter, token)) { - mCurrentVibration.notifyCancelled(/* immediate= */false); + mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, + /* immediate= */false); } if (mCurrentExternalVibration != null && shouldCancelVibration( mCurrentExternalVibration.externalVibration.getVibrationAttributes(), usageFilter)) { mCurrentExternalVibration.externalVibration.mute(); - endExternalVibrateLocked(Vibration.Status.CANCELLED, + endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER, /* continueExternalControl= */ false); } } finally { @@ -594,7 +603,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.d(TAG, "Canceling vibration because settings changed: " + (inputDevicesChanged ? "input devices changed" : ignoreStatus)); } - mCurrentVibration.notifyCancelled(/* immediate= */ false); + mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, + /* immediate= */ false); } } } @@ -1313,7 +1323,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (DEBUG) { Slog.d(TAG, "External vibration finished because binder died"); } - endExternalVibrateLocked(Vibration.Status.CANCELLED, + endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED, /* continueExternalControl= */ false); } } @@ -1506,12 +1516,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return IExternalVibratorService.SCALE_MUTE; } + VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(), + /* effect= */ null); + if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { + // Force update of user settings before checking if this vibration effect should + // be ignored or scaled. + mVibrationSettings.update(); + } + boolean alreadyUnderExternalControl = false; boolean waitForCompletion = false; int scale; synchronized (mLock) { Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( - vib.getUid(), vib.getPackage(), vib.getVibrationAttributes()); + vib.getUid(), vib.getPackage(), attrs); if (ignoreStatus != null) { ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); vibHolder.scale = IExternalVibratorService.SCALE_MUTE; @@ -1529,7 +1547,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // vibration that may be playing and ready the vibrator for external control. if (mCurrentVibration != null) { clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL); - mCurrentVibration.notifyCancelled(/* immediate= */ true); + mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, + /* immediate= */ true); waitForCompletion = true; } } else { @@ -1543,13 +1562,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // would need to mute the old one still if it came from a different controller. alreadyUnderExternalControl = true; mCurrentExternalVibration.externalVibration.mute(); - endExternalVibrateLocked(Vibration.Status.CANCELLED, + endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED, /* continueExternalControl= */ true); } mCurrentExternalVibration = new ExternalVibrationHolder(vib); vib.linkToDeath(mCurrentExternalVibration); mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( - vib.getVibrationAttributes().getUsage()); + attrs.getUsage()); scale = mCurrentExternalVibration.scale; } @@ -1908,7 +1927,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { final int flags = commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0; return new VibrationAttributes.Builder() - .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) + .setFlags(flags) // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects. .setUsage(VibrationAttributes.USAGE_TOUCH) .build(); diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index 11ddac6a9740..799d59c617ea 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -811,8 +811,8 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { RectF windowFrame = TEMP_RECTF; windowFrame.set(rect); - inverseMatrix.mapRect(windowFrame); displayMatrix.mapRect(windowFrame); + inverseMatrix.mapRect(windowFrame); // Union all rects. outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top, (int) windowFrame.right, (int) windowFrame.bottom)); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 4bef126ee2c8..2c9372e7eaca 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -116,6 +116,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_OLD_UNSET; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; @@ -216,7 +217,6 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.ACTIVITY; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; @@ -7237,11 +7237,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getDisplayContent().computeImeTargetIfNeeded(this); - if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this - + ": reportedVisible=" + reportedVisible - + " okToDisplay=" + okToDisplay() - + " okToAnimate=" + okToAnimate() - + " startingDisplayed=" + startingDisplayed); + ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s" + + ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b", + this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed); // clean up thumbnail window if (mThumbnail != null) { diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index 8622bd32fc68..ce49a8675890 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -18,20 +18,11 @@ package com.android.server.wm; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; -import android.os.IBinder; import android.os.InputConfig; -import android.os.InputConstants; -import android.os.Looper; import android.os.Process; -import android.util.Slog; -import android.view.InputChannel; -import android.view.InputEvent; -import android.view.InputEventReceiver; import android.view.InputWindowHandle; -import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.WindowManager; -import android.widget.Toast; /** * Creates a InputWindowHandle that catches all touches that would otherwise pass through an @@ -45,20 +36,12 @@ class ActivityRecordInputSink { @ChangeId static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L; - private static final String TAG = "ActivityRecordInputSink"; - private static final int NUMBER_OF_TOUCHES_TO_DISABLE = 3; - private static final long TOAST_COOL_DOWN_MILLIS = 3000L; - private final ActivityRecord mActivityRecord; private final boolean mIsCompatEnabled; private final String mName; - // Hold on to InputEventReceiver to prevent it from getting GCd. - private InputEventReceiver mInputEventReceiver; private InputWindowHandleWrapper mInputWindowHandleWrapper; private SurfaceControl mSurfaceControl; - private int mRapidTouchCount = 0; - private IBinder mToken; private boolean mDisabled = false; ActivityRecordInputSink(ActivityRecord activityRecord) { @@ -66,7 +49,7 @@ class ActivityRecordInputSink { mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES, mActivityRecord.getUid()); mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink " - + mActivityRecord.mActivityComponent.getShortClassName(); + + mActivityRecord.mActivityComponent.flattenToShortString(); } public void applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction) { @@ -93,16 +76,13 @@ class ActivityRecordInputSink { private InputWindowHandleWrapper getInputWindowHandleWrapper() { if (mInputWindowHandleWrapper == null) { mInputWindowHandleWrapper = new InputWindowHandleWrapper(createInputWindowHandle()); - InputChannel inputChannel = - mActivityRecord.mWmService.mInputManager.createInputChannel(mName); - mToken = inputChannel.getToken(); - mInputEventReceiver = createInputEventReceiver(inputChannel); } if (mDisabled || !mIsCompatEnabled || mActivityRecord.isInTransition()) { // TODO(b/208662670): Investigate if we can have feature active during animations. - mInputWindowHandleWrapper.setToken(null); + mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE, + InputConfig.NOT_TOUCHABLE); } else { - mInputWindowHandleWrapper.setToken(mToken); + mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE); } return mInputWindowHandleWrapper; } @@ -115,58 +95,8 @@ class ActivityRecordInputSink { inputWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; inputWindowHandle.ownerUid = Process.myUid(); inputWindowHandle.ownerPid = Process.myPid(); - inputWindowHandle.dispatchingTimeoutMillis = - InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; - inputWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE; + inputWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.NO_INPUT_CHANNEL; return inputWindowHandle; } - private InputEventReceiver createInputEventReceiver(InputChannel inputChannel) { - return new SinkInputEventReceiver(inputChannel, - mActivityRecord.mAtmService.mUiHandler.getLooper()); - } - - private void showAsToastAndLog(String message) { - Toast.makeText(mActivityRecord.mAtmService.mUiContext, message, - Toast.LENGTH_LONG).show(); - Slog.wtf(TAG, message + " " + mActivityRecord.mActivityComponent); - } - - private class SinkInputEventReceiver extends InputEventReceiver { - private long mLastToast = 0; - - SinkInputEventReceiver(InputChannel inputChannel, Looper looper) { - super(inputChannel, looper); - } - - public void onInputEvent(InputEvent event) { - if (!(event instanceof MotionEvent)) { - Slog.wtf(TAG, - "Received InputEvent that was not a MotionEvent"); - finishInputEvent(event, true); - return; - } - MotionEvent motionEvent = (MotionEvent) event; - if (motionEvent.getAction() != MotionEvent.ACTION_DOWN) { - finishInputEvent(event, true); - return; - } - - if (event.getEventTime() - mLastToast > TOAST_COOL_DOWN_MILLIS) { - String message = "go/activity-touch-opaque - " - + mActivityRecord.mActivityComponent.getPackageName() - + " blocked the touch!"; - showAsToastAndLog(message); - mLastToast = event.getEventTime(); - mRapidTouchCount = 1; - } else if (++mRapidTouchCount >= NUMBER_OF_TOUCHES_TO_DISABLE && !mDisabled) { - // Disable touch blocking until Activity Record is recreated. - String message = "Disabled go/activity-touch-opaque - " - + mActivityRecord.mActivityComponent.getPackageName(); - showAsToastAndLog(message); - mDisabled = true; - } - finishInputEvent(event, true); - } - } } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 7abcc4b808a1..e50aff438ecd 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2687,11 +2687,8 @@ class ActivityStarter { // launched into the same root task. mTargetRootTask = Task.fromWindowContainerToken(mSourceRecord.mLaunchRootTask); } else { - final Task rootTask = - getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask, mOptions); - // TODO(b/184806710): #getOrCreateRootTask should never return null? - mTargetRootTask = - rootTask != null ? rootTask : intentActivity.getRootTask(); + mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask, + mOptions); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 8b6262f0fcca..ad6f35452051 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5303,14 +5303,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** * Returns {@code true} if the process represented by the pid passed as argument is - * instrumented. + * instrumented and the instrumentation source was granted with the permission also + * passed as argument. */ - boolean isInstrumenting(int pid) { + boolean instrumentationSourceHasPermission(int pid, String permission) { final WindowProcessController process; synchronized (mGlobalLock) { process = mProcessMap.getProcess(pid); } - return process != null ? process.isInstrumenting() : false; + if (process == null || !process.isInstrumenting()) { + return false; + } + final int sourceUid = process.getInstrumentationSourceUid(); + return checkPermission(permission, -1, sourceUid) == PackageManager.PERMISSION_GRANTED; } final class H extends Handler { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 05efb29cc775..5c09f09d9313 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -81,11 +81,11 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE; import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; @@ -130,6 +130,7 @@ import android.view.animation.TranslateAnimation; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.TransitionAnimation; +import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.DumpUtils.Dump; import com.android.internal.util.function.pooled.PooledLambda; @@ -237,7 +238,8 @@ public class AppTransition implements Dump { mService = service; mHandler = new Handler(service.mH.getLooper()); mDisplayContent = displayContent; - mTransitionAnimation = new TransitionAnimation(context, DEBUG_ANIM, TAG); + mTransitionAnimation = new TransitionAnimation( + context, ProtoLogImpl.isEnabled(WM_DEBUG_ANIM), TAG); mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false); @@ -1305,6 +1307,8 @@ public class AppTransition implements Dump { pw.print(Integer.toHexString(mNextAppTransitionEnter)); pw.print(" mNextAppTransitionExit=0x"); pw.println(Integer.toHexString(mNextAppTransitionExit)); + pw.print(" mNextAppTransitionBackgroundColor=0x"); + pw.println(Integer.toHexString(mNextAppTransitionBackgroundColor)); } switch (mNextAppTransitionType) { case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE: diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f5ace6c78288..61cd22170e18 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -129,7 +129,6 @@ import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA; import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION; import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS; 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.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -3974,7 +3973,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean nonAppImeTargetAnimatingExit = mImeLayeringTarget.mAnimatingExit && mImeLayeringTarget.mAttrs.type != TYPE_BASE_APPLICATION && mImeLayeringTarget.isSelfAnimating(0, ANIMATION_TYPE_WINDOW_ANIMATION); - if (mImeLayeringTarget.inAppOrRecentsTransition() || nonAppImeTargetAnimatingExit) { + if (mImeLayeringTarget.inTransitionSelfOrParent() || nonAppImeTargetAnimatingExit) { showImeScreenshot(); } } @@ -4045,11 +4044,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private SurfaceControl createImeSurface(SurfaceControl.ScreenshotHardwareBuffer b, Transaction t) { final HardwareBuffer buffer = b.getHardwareBuffer(); - if (DEBUG_INPUT_METHOD) { - Slog.d(TAG, "create IME snapshot for " - + mImeTarget + ", buff width=" + buffer.getWidth() - + ", height=" + buffer.getHeight()); - } + ProtoLog.i(WM_DEBUG_IME, "create IME snapshot for %s, buff width=%s, height=%s", + mImeTarget, buffer.getWidth(), buffer.getHeight()); final WindowState imeWindow = mImeTarget.getDisplayContent().mInputMethodWindow; final ActivityRecord activity = mImeTarget.mActivityRecord; final SurfaceControl imeParent = mImeTarget.mAttrs.type == TYPE_BASE_APPLICATION @@ -4085,12 +4081,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mImeTarget.mAttrs.surfaceInsets.top); t.setPosition(imeSurface, surfacePosition.x, surfacePosition.y); } + ProtoLog.i(WM_DEBUG_IME, "Set IME snapshot position: (%d, %d)", surfacePosition.x, + surfacePosition.y); return imeSurface; } private void removeImeSurface(Transaction t) { if (mImeSurface != null) { - if (DEBUG_INPUT_METHOD) Slog.d(TAG, "remove IME snapshot"); + ProtoLog.i(WM_DEBUG_IME, "remove IME snapshot, caller=%s", Debug.getCallers(6)); t.remove(mImeSurface); mImeSurface = null; } @@ -4120,9 +4118,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // to reflect the true IME insets visibility and the app task layout as possible. if (isValidSnapshot && dc.getInsetsStateController().getImeSourceProvider().isImeShowing()) { - if (DEBUG_INPUT_METHOD) { - Slog.d(TAG, "show IME snapshot, ime target=" + mImeTarget); - } + ProtoLog.i(WM_DEBUG_IME, "show IME snapshot, ime target=%s, callers=%s", + mImeTarget, Debug.getCallers(6)); t.show(mImeSurface); } else if (!isValidSnapshot) { removeImeSurface(t); @@ -4168,7 +4165,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void removeImeScreenshotIfPossible() { if (mImeLayeringTarget == null || mImeLayeringTarget.mAttrs.type != TYPE_APPLICATION_STARTING - && !mImeLayeringTarget.inAppOrRecentsTransition()) { + && !mImeLayeringTarget.inTransitionSelfOrParent()) { removeImeSurfaceImmediately(); } } @@ -4489,6 +4486,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * hierarchy. */ void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) { + if (mImeScreenshot != null) { + ProtoLog.i(WM_DEBUG_IME, + "onWindowAnimationFinished, wc=%s, type=%s, imeSnapshot=%s, target=%s", + wc, SurfaceAnimator.animationTypeToString(type), mImeScreenshot, + mImeScreenshot.getImeTarget()); + } if (mImeScreenshot != null && (wc == mImeScreenshot.getImeTarget() || wc.getWindow(w -> w == mImeScreenshot.getImeTarget()) != null) && (type & WindowState.EXIT_ANIMATING_TYPES) != 0) { @@ -4876,6 +4879,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) { super.setOrganizer(organizer, skipDisplayAreaAppeared); mDisplayContent.updateImeParent(); + + // If the ImeContainer was previously unorganized then the framework might have + // reparented its surface control under an activity so we need to reparent it back + // under its parent. + if (organizer != null) { + final SurfaceControl imeParentSurfaceControl = getParentSurfaceControl(); + if (mSurfaceControl != null && imeParentSurfaceControl != null) { + ProtoLog.i(WM_DEBUG_IME, "ImeContainer just became organized. Reparenting " + + "under parent. imeParentSurfaceControl=%s", imeParentSurfaceControl); + getPendingTransaction().reparent(mSurfaceControl, imeParentSurfaceControl); + } else { + ProtoLog.e(WM_DEBUG_IME, "ImeContainer just became organized but it doesn't " + + "have a parent or the parent doesn't have a surface control." + + " mSurfaceControl=%s imeParentSurfaceControl=%s", + mSurfaceControl, imeParentSurfaceControl); + } + } } } @@ -5011,11 +5031,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp ? mImeControlTarget.getWindow().mToken : null; final boolean canImeTargetSetRelativeLayer = imeTarget.getSurfaceControl() != null && imeTarget.mToken == imeControlTargetToken - && !imeTarget.inMultiWindowMode() - // We don't need to set relative layer if the IME target in non-multi-window - // mode is the activity main window since updateImeParent will ensure the IME - // surface be attached on the fullscreen activity. - && imeTarget.mAttrs.type != TYPE_BASE_APPLICATION; + && !imeTarget.inMultiWindowMode(); if (canImeTargetSetRelativeLayer) { mImeWindowsContainer.assignRelativeLayer(t, imeTarget.getSurfaceControl(), // TODO: We need to use an extra level on the app surface to ensure diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 88d7dff4ff1a..4573ede13f7f 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -75,6 +75,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; @@ -83,7 +84,6 @@ import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -1366,8 +1366,7 @@ public class DisplayPolicy { * @return Resource ID of the actual animation to use, or {@link #ANIMATION_NONE} for none. */ int selectAnimation(WindowState win, int transit) { - if (DEBUG_ANIM) Slog.i(TAG, "selectAnimation in " + win - + ": transit=" + transit); + ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit); if (win == mStatusBar) { if (transit == TRANSIT_EXIT || transit == TRANSIT_HIDE) { @@ -1460,7 +1459,7 @@ public class DisplayPolicy { // with old content because home is easier to have different UI states. return ANIMATION_NONE; } - if (DEBUG_ANIM) Slog.i(TAG, "**** STARTING EXIT"); + ProtoLog.i(WM_DEBUG_ANIM, "**** STARTING EXIT"); return R.anim.app_starting_exit; } } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 262ddae02765..43ff58083657 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE; @@ -32,7 +33,6 @@ import static com.android.server.wm.DisplayRotationProto.IS_FIXED_TO_USER_ROTATI import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION; import static com.android.server.wm.DisplayRotationProto.ROTATION; import static com.android.server.wm.DisplayRotationProto.USER_ROTATION; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE; @@ -747,10 +747,11 @@ public class DisplayRotation { final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully() || !mService.mPolicy.okToAnimate(false /* ignoreScreenOn */); final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow(); - if (DEBUG_ANIM) Slog.i(TAG, "selectRotationAnimation topFullscreen=" - + topFullscreen + " rotationAnimation=" - + (topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation) - + " forceJumpcut=" + forceJumpcut); + ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s" + + " rotationAnimation=%d forceJumpcut=%b", + topFullscreen, + topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation, + forceJumpcut); if (forceJumpcut) { mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit; mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 8866343afe03..0038c716fee7 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -145,6 +145,14 @@ final class LetterboxUiController { return; } updateRoundedCorners(w); + // If there is another main window that is not an application-starting window, we should + // update rounded corners for it as well, to avoid flickering rounded corners. + final WindowState nonStartingAppW = mActivityRecord.findMainWindow( + /* includeStartingApp= */ false); + if (nonStartingAppW != null && nonStartingAppW != w) { + updateRoundedCorners(nonStartingAppW); + } + updateWallpaperForLetterbox(w); if (shouldShowLetterboxUi(w)) { if (mLetterbox == null) { diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index 61f9fe29b2f8..d7e725b5e679 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -174,5 +174,9 @@ class LocalAnimationAdapter implements AnimationAdapter { } void dumpDebugInner(ProtoOutputStream proto); + + default WindowAnimationSpec asWindowAnimationSpec() { + return null; + } } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index a4d338c06708..e21ae05c0827 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -281,6 +281,7 @@ public class RecentsAnimationController implements DeathRecipient { task.setCanAffectSystemUiFlags(behindSystemBars); } } + InputMethodManagerInternal.get().maybeFinishStylusHandwriting(); if (!behindSystemBars) { // Hiding IME if IME window is not attached to app. // Since some windowing mode is not proper to snapshot Task with IME window diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index eeac230489f9..992778794203 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -110,7 +110,7 @@ class RemoteAnimationController implements DeathRecipient { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): Animation canceled already"); onAnimationFinished(); - invokeAnimationCancelled(); + invokeAnimationCancelled("already_cancelled"); return; } @@ -129,7 +129,7 @@ class RemoteAnimationController implements DeathRecipient { "goodToGo(): No apps to animate, mPendingAnimations=%d", mPendingAnimations.size()); onAnimationFinished(); - invokeAnimationCancelled(); + invokeAnimationCancelled("no_app_targets"); return; } @@ -169,7 +169,7 @@ class RemoteAnimationController implements DeathRecipient { mCanceled = true; } onAnimationFinished(); - invokeAnimationCancelled(); + invokeAnimationCancelled(reason); } private void writeStartDebugStatement() { @@ -296,7 +296,8 @@ class RemoteAnimationController implements DeathRecipient { ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation"); } - private void invokeAnimationCancelled() { + private void invokeAnimationCancelled(String reason) { + ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason); try { mRemoteAnimationAdapter.getRunner().onAnimationCancelled(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 94fc51dc94d2..cc99f377bfee 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2754,7 +2754,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // First preference goes to the launch root task set in the activity options. if (options != null) { final Task candidateRoot = Task.fromWindowContainerToken(options.getLaunchRootTask()); - if (canLaunchOnDisplay(r, candidateRoot)) { + if (candidateRoot != null && canLaunchOnDisplay(r, candidateRoot)) { return candidateRoot; } } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 92e2ee6f8af9..b57670914c11 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -26,13 +26,22 @@ import android.animation.Animator; 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.util.ArrayMap; +import android.util.Log; import android.view.Choreographer; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.view.animation.Animation; +import android.view.animation.Transformation; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -40,6 +49,8 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.server.AnimationThread; import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; +import java.util.ArrayList; +import java.util.List; import java.util.function.Supplier; /** @@ -73,6 +84,10 @@ class SurfaceAnimationRunner { @GuardedBy("mLock") @VisibleForTesting + final ArrayMap<SurfaceControl, RunningAnimation> mPreProcessingAnimations = new ArrayMap<>(); + + @GuardedBy("mLock") + @VisibleForTesting final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>(); @GuardedBy("mLock") @@ -136,23 +151,64 @@ class SurfaceAnimationRunner { synchronized (mLock) { final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash, finishCallback); - mPendingAnimations.put(animationLeash, runningAnim); - if (!mAnimationStartDeferred) { - mChoreographer.postFrameCallback(this::startAnimations); + boolean requiresEdgeExtension = requiresEdgeExtension(a); + + if (requiresEdgeExtension) { + mPreProcessingAnimations.put(animationLeash, runningAnim); + + // 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, () -> { + final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec(); + final Runnable cleanUpEdgeExtension = edgeExtendWindow(animationLeash, + animationSpec.getRootTaskBounds(), animationSpec.getAnimation(), + mFrameTransaction); + + runningAnim.mFinishCallback = () -> { + cleanUpEdgeExtension.run(); + finishCallback.run(); + }; + + synchronized (mLock) { + // only run if animation is not yet canceled by this point + if (mPreProcessingAnimations.get(animationLeash) == runningAnim) { + mPreProcessingAnimations.remove(animationLeash); + mPendingAnimations.put(animationLeash, runningAnim); + if (!mAnimationStartDeferred) { + mChoreographer.postFrameCallback(this::startAnimations); + } + } + } + }); } - // Some animations (e.g. move animations) require the initial transform to be applied - // immediately. - applyTransformation(runningAnim, t, 0 /* currentPlayTime */); + if (!requiresEdgeExtension) { + mPendingAnimations.put(animationLeash, runningAnim); + if (!mAnimationStartDeferred) { + mChoreographer.postFrameCallback(this::startAnimations); + } + + // Some animations (e.g. move animations) require the initial transform to be + // applied immediately. + applyTransformation(runningAnim, t, 0 /* currentPlayTime */); + } } } + private boolean requiresEdgeExtension(AnimationSpec a) { + return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension(); + } + void onAnimationCancelled(SurfaceControl leash) { synchronized (mLock) { if (mPendingAnimations.containsKey(leash)) { mPendingAnimations.remove(leash); return; } + if (mPreProcessingAnimations.containsKey(leash)) { + mPreProcessingAnimations.remove(leash); + return; + } final RunningAnimation anim = mRunningAnimations.get(leash); if (anim != null) { mRunningAnimations.remove(leash); @@ -264,10 +320,133 @@ class SurfaceAnimationRunner { mApplyScheduled = false; } + private Runnable edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a, + Transaction transaction) { + final Transformation transformationAtStart = new Transformation(); + a.getTransformationAt(0, transformationAtStart); + final Transformation transformationAtEnd = new Transformation(); + a.getTransformationAt(1, transformationAtEnd); + + // We want to create an extension surface that is the maximal size and the animation will + // take care of cropping any part that overflows. + final Insets maxExtensionInsets = Insets.min( + transformationAtStart.getInsets(), transformationAtEnd.getInsets()); + + final int targetSurfaceHeight = bounds.height(); + final int targetSurfaceWidth = bounds.width(); + + final List<SurfaceControl> extensionSurfaces = new ArrayList<>(); + + if (maxExtensionInsets.left < 0) { + final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.left, targetSurfaceHeight); + final int xPos = maxExtensionInsets.left; + final int yPos = 0; + final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds, + extensionRect, xPos, yPos, "Left Edge Extension", transaction); + extensionSurfaces.add(extensionSurface); + } + + if (maxExtensionInsets.top < 0) { + final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.top); + final int xPos = 0; + final int yPos = maxExtensionInsets.top; + final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds, + extensionRect, xPos, yPos, "Top Edge Extension", transaction); + extensionSurfaces.add(extensionSurface); + } + + if (maxExtensionInsets.right < 0) { + final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.right, targetSurfaceHeight); + final int xPos = targetSurfaceWidth; + final int yPos = 0; + final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds, + extensionRect, xPos, yPos, "Right Edge Extension", transaction); + extensionSurfaces.add(extensionSurface); + } + + if (maxExtensionInsets.bottom < 0) { + final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.bottom); + final int xPos = maxExtensionInsets.left; + final int yPos = targetSurfaceHeight; + final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds, + extensionRect, xPos, yPos, "Bottom Edge Extension", transaction); + extensionSurfaces.add(extensionSurface); + } + + Runnable cleanUp = () -> { + for (final SurfaceControl extensionSurface : extensionSurfaces) { + if (extensionSurface != null) { + transaction.remove(extensionSurface); + } + } + }; + + return cleanUp; + } + + private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, 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) + .setSourceCrop(edgeBounds) + .setFrameScale(1) + .setPixelFormat(PixelFormat.RGBA_8888) + .setChildrenOnly(true) + .setAllowProtected(true) + .build(); + final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = + SurfaceControl.captureLayers(captureArgs); + + if (edgeBuffer == null) { + Log.e("SurfaceAnimationRunner", "Failed to create edge extension - " + + "edge buffer is null"); + return null; + } + + 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 Surface surface = new Surface(edgeExtensionLayer); + Canvas c = surface.lockHardwareCanvas(); + c.drawRect(extensionRect, paint); + surface.unlockCanvasAndPost(c); + surface.release(); + + startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); + startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); + startTransaction.setVisibility(edgeExtensionLayer, true); + + return edgeExtensionLayer; + } + private static final class RunningAnimation { final AnimationSpec mAnimSpec; final SurfaceControl mLeash; - final Runnable mFinishCallback; + Runnable mFinishCallback; ValueAnimator mAnim; @GuardedBy("mCancelLock") diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 53e33781bca6..fbf04262cc37 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -16,10 +16,10 @@ package com.android.server.wm; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_ADAPTER; import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_START_DELAYED; import static com.android.server.wm.SurfaceAnimatorProto.LEASH; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -32,8 +32,11 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLogImpl; +import com.android.internal.protolog.common.ProtoLog; import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.function.Supplier; @@ -185,10 +188,16 @@ class SurfaceAnimator { } mAnimatable.onLeashAnimationStarting(t, mLeash); if (mAnimationStartDelayed) { - if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed"); + ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable); return; } mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback); + if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + mAnimation.dump(pw, ""); + ProtoLog.d(WM_DEBUG_ANIM, "Animation start for %s, anim=%s", mAnimatable, sw); + } if (snapshotAnim != null) { mSnapshot = freezer.takeSnapshotForAnimation(); if (mSnapshot == null) { @@ -340,7 +349,8 @@ class SurfaceAnimator { * to another animator. */ private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) { - if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting); + ProtoLog.i(WM_DEBUG_ANIM, "Cancelling animation restarting=%b for %s", + restarting, mAnimatable); final SurfaceControl leash = mLeash; final AnimationAdapter animation = mAnimation; final @AnimationType int animationType = mAnimationType; @@ -419,7 +429,8 @@ class SurfaceAnimator { final boolean reparent = surface != null && (curAnimationLeash == null || curAnimationLeash.equals(leash)); if (reparent) { - if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent); + ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to original parent: %s for %s", + parent, animatable); // We shouldn't really need these isValid checks but we do // b/130364451 if (surface.isValid() && parent != null && parent.isValid()) { @@ -444,7 +455,7 @@ class SurfaceAnimator { static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface, Transaction t, @AnimationType int type, int width, int height, int x, int y, boolean hidden, Supplier<Transaction> transactionFactory) { - if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash"); + ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable); final SurfaceControl.Builder builder = animatable.makeAnimationLeash() .setParent(animatable.getAnimationLeashParent()) .setName(surface + " - animation-leash of " + animationTypeToString(type)) diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index c0bc0191b16d..23df429c3f24 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -405,6 +405,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: " + mResumedActivity + " to:" + r + " reason:" + reason); } + + if (r != null && mResumedActivity == null) { + // Task is becoming active. + getTask().touchActiveTime(); + } + final ActivityRecord prevR = mResumedActivity; mResumedActivity = r; mTaskSupervisor.updateTopResumedActivityIfNeeded(); @@ -518,7 +524,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { || isAllowedToEmbedActivityInTrustedMode(a); } + /** + * Checks if the organized task fragment is allowed to embed activity in untrusted mode. + */ boolean isAllowedToEmbedActivityInUntrustedMode(@NonNull ActivityRecord a) { + final WindowContainer parent = getParent(); + if (parent == null || !parent.getBounds().contains(getBounds())) { + // Without full trust between the host and the embedded activity, we don't allow + // TaskFragment to have bounds outside of the parent bounds. + return false; + } return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index ff5bfbee61f4..7d738241e4a3 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -67,7 +67,8 @@ import java.util.function.Consumer; class TaskOrganizerController extends ITaskOrganizerController.Stub { private static final String TAG = "TaskOrganizerController"; - private class DeathRecipient implements IBinder.DeathRecipient { + @VisibleForTesting + class DeathRecipient implements IBinder.DeathRecipient { ITaskOrganizer mTaskOrganizer; DeathRecipient(ITaskOrganizer organizer) { @@ -77,7 +78,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { @Override public void binderDied() { synchronized (mGlobalLock) { - final TaskOrganizerState state = mTaskOrganizerStates.remove( + final TaskOrganizerState state = mTaskOrganizerStates.get( mTaskOrganizer.asBinder()); if (state != null) { state.dispose(); @@ -170,7 +171,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } - private class TaskOrganizerState { + @VisibleForTesting + class TaskOrganizerState { private final TaskOrganizerCallbacks mOrganizer; private final DeathRecipient mDeathRecipient; private final ArrayList<Task> mOrganizedTasks = new ArrayList<>(); @@ -191,6 +193,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mUid = uid; } + @VisibleForTesting + DeathRecipient getDeathRecipient() { + return mDeathRecipient; + } + /** * Register this task with this state, but doesn't trigger the task appeared callback to * the organizer. @@ -265,7 +272,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // Remove organizer state after removing tasks so we get a chance to send // onTaskVanished. - mTaskOrganizerStates.remove(asBinder()); + mTaskOrganizerStates.remove(mOrganizer.getBinder()); } void unlinkDeath() { @@ -596,7 +603,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { private void onTaskVanishedInternal(ITaskOrganizer organizer, Task task) { for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) { PendingTaskEvent entry = mPendingTaskEvents.get(i); - if (task.mTaskId == entry.mTask.mTaskId) { + if (task.mTaskId == entry.mTask.mTaskId && entry.mTaskOrg == organizer) { // This task is vanished so remove all pending event of it. mPendingTaskEvents.remove(i); if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) { @@ -693,9 +700,16 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } break; case PendingTaskEvent.EVENT_VANISHED: - state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder()); - if (state != null) { - state.mOrganizer.onTaskVanished(task); + // TaskOrganizerState cannot be used here because it might have already been + // removed. + // The state is removed when an organizer dies or is unregistered. In order to + // send the pending vanished task events, the mTaskOrg from event is used. + // These events should not ideally be sent and will be removed as part of + // b/224812558. + try { + event.mTaskOrg.onTaskVanished(task.getTaskInfo()); + } catch (RemoteException ex) { + Slog.e(TAG, "Exception sending onTaskVanished callback", ex); } mLastSentTaskInfos.remove(task); break; @@ -1045,4 +1059,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } pw.println(); } + + @VisibleForTesting + TaskOrganizerState getTaskOrganizerState(IBinder taskOrganizer) { + return mTaskOrganizerStates.get(taskOrganizer); + } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 8840cd557de6..18851b34ec04 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -243,9 +243,13 @@ class TransitionController { return isCollecting() || isPlaying(); } - /** @return {@code true} if wc is in a participant subtree */ + /** @return {@code true} if a transition is running in a participant subtree of wc */ boolean inTransition(@NonNull WindowContainer wc) { - if (isCollecting(wc)) return true; + if (isCollecting()) { + for (WindowContainer p = wc; p != null; p = p.getParent()) { + if (isCollecting(p)) return true; + } + } for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { for (WindowContainer p = wc; p != null; p = p.getParent()) { if (mPlayingTransitions.get(i).mParticipants.contains(p)) { diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java index 5bfd546f7620..b6d668dde9d8 100644 --- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java @@ -21,6 +21,7 @@ import static com.android.server.wm.AnimationSpecProto.WINDOW; import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE; +import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.os.SystemClock; @@ -75,6 +76,11 @@ public class WindowAnimationSpec implements AnimationSpec { } @Override + public WindowAnimationSpec asWindowAnimationSpec() { + return this; + } + + @Override public boolean getShowWallpaper() { return mAnimation.getShowWallpaper(); } @@ -89,11 +95,27 @@ public class WindowAnimationSpec implements AnimationSpec { return mAnimation.getBackgroundColor(); } + /** + * @return If a window animation has outsets applied to it. + * @see Animation#hasExtension() + */ + public boolean hasExtension() { + return mAnimation.hasExtension(); + } + @Override public long getDuration() { return mAnimation.computeDurationHint(); } + public Rect getRootTaskBounds() { + return mRootTaskBounds; + } + + public Animation getAnimation() { + return mAnimation; + } + @Override public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) { final TmpValues tmp = mThreadLocalTmps.get(); @@ -106,7 +128,9 @@ public class WindowAnimationSpec implements AnimationSpec { boolean cropSet = false; if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) { if (tmp.transformation.hasClipRect()) { - t.setWindowCrop(leash, tmp.transformation.getClipRect()); + final Rect clipRect = tmp.transformation.getClipRect(); + accountForExtension(tmp.transformation, clipRect); + t.setWindowCrop(leash, clipRect); cropSet = true; } } else { @@ -114,6 +138,7 @@ public class WindowAnimationSpec implements AnimationSpec { if (tmp.transformation.hasClipRect()) { mTmpRect.intersect(tmp.transformation.getClipRect()); } + accountForExtension(tmp.transformation, mTmpRect); t.setWindowCrop(leash, mTmpRect); cropSet = true; } @@ -125,6 +150,14 @@ public class WindowAnimationSpec implements AnimationSpec { } } + private void accountForExtension(Transformation transformation, Rect clipRect) { + Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE); + if (!extensionInsets.equals(Insets.NONE)) { + // Extend the surface to allow for the edge extension to be visible + clipRect.inset(extensionInsets); + } + } + @Override public long calculateStatusBarTransitionStartTime() { TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e7b4e834eb5a..99e39f1969e1 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -33,6 +33,7 @@ import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.TRANSIT_CHANGE; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; @@ -47,7 +48,6 @@ import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; 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.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -59,10 +59,8 @@ import static com.android.server.wm.WindowContainerProto.SURFACE_ANIMATOR; import static com.android.server.wm.WindowContainerProto.SURFACE_CONTROL; import static com.android.server.wm.WindowContainerProto.VISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.logWithStack; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM; import android.annotation.CallSuper; @@ -107,6 +105,7 @@ import android.window.WindowContainerToken; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; +import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.wm.SurfaceAnimator.Animatable; @@ -1175,27 +1174,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mTransitionController.inTransition(this); } - boolean inAppOrRecentsTransition() { - if (!mTransitionController.isShellTransitionsEnabled()) { - return isAnimating(PARENTS | TRANSITION, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); - } - for (WindowContainer p = this; p != null; p = p.getParent()) { - if (mTransitionController.isCollecting(p)) { - return true; - } - } - if (inTransition() || mTransitionController.inRecentsTransition(this)) return true; - - for (int i = mChildren.size() - 1; i >= 0; --i) { - WindowContainer child = mChildren.get(i); - if (child.inAppOrRecentsTransition()) { - return true; - } - } - return false; - } - boolean isExitAnimationRunningSelfOrChild() { if (!mTransitionController.isShellTransitionsEnabled()) { return isAnimating(TRANSITION | CHILDREN, WindowState.EXIT_ANIMATING_TYPES); @@ -2748,9 +2726,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Nullable OnAnimationFinishedCallback animationFinishedCallback, @Nullable Runnable animationCancelledCallback, @Nullable AnimationAdapter snapshotAnim) { - if (DEBUG_ANIM) { - Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim); - } + ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s", + this, type, anim); // TODO: This should use isVisible() but because isVisible has a really weird meaning at // the moment this doesn't work for all animatable window containers. @@ -3119,9 +3096,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition. a.restrictDuration(MAX_APP_TRANSITION_DURATION); } - if (DEBUG_ANIM) { - logWithStack(TAG, "Loaded animation " + a + " for " + this - + ", duration: " + ((a != null) ? a.getDuration() : 0)); + if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) { + ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s", + a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20)); } final int containingWidth = frame.width(); final int containingHeight = frame.height(); @@ -3893,7 +3870,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< try { overlay.getRemoteInterface().onConfigurationChanged(getConfiguration()); } catch (Exception e) { - Slog.e(TAG, "Error sending initial configuration change to WindowContainer overlay"); + ProtoLog.e(WM_DEBUG_ANIM, + "Error sending initial configuration change to WindowContainer overlay"); removeTrustedOverlay(overlay); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java index c95470071e2d..42b556f77ab6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java @@ -34,7 +34,6 @@ public class WindowManagerDebugConfig { static final String TAG_WM = "WindowManager"; static final boolean DEBUG = false; - static final boolean DEBUG_ANIM = false; static final boolean DEBUG_LAYOUT = false; static final boolean DEBUG_LAYERS = false; static final boolean DEBUG_INPUT = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 43e84ae91118..dd2941c75346 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -97,6 +97,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; @@ -2560,7 +2561,9 @@ public class WindowManagerService extends IWindowManager.Stub transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } + String reason = null; if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { + reason = "applyAnimation"; focusMayChange = true; win.mAnimatingExit = true; } else if (win.mDisplayContent.okToAnimate() && win.isExitAnimationRunningSelfOrParent()) { @@ -2570,6 +2573,7 @@ public class WindowManagerService extends IWindowManager.Stub } else if (win.mDisplayContent.okToAnimate() && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win) && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) { + reason = "isWallpaperTarget"; // If the wallpaper is currently behind this app window, we need to change both of them // inside of a transaction to avoid artifacts. // For NotificationShade, sysui is in charge of running window animation and it updates @@ -2585,6 +2589,10 @@ public class WindowManagerService extends IWindowManager.Stub win.mDestroying = true; win.destroySurface(false, stopped); } + if (reason != null) { + ProtoLog.d(WM_DEBUG_ANIM, "Set animatingExit: reason=startExitingAnimation/%s win=%s", + reason, win); + } if (mAccessibilityController.hasCallbacks()) { mAccessibilityController.onWindowTransition(win, transit); } @@ -3760,7 +3768,8 @@ public class WindowManagerService extends IWindowManager.Stub * Sets the touch mode state. * * To be able to change touch mode state, the caller must either own the focused window, or must - * have the MODIFY_TOUCH_MODE_STATE permission. Instrumented processes are allowed to switch + * have the {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE} permission. Instrumented + * process, sourced with {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE}, may switch * touch mode at any time. * * @param mode the touch mode to set @@ -3773,9 +3782,9 @@ public class WindowManagerService extends IWindowManager.Stub } final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); - - final boolean hasPermission = mAtmService.isInstrumenting(pid) - || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()"); + final boolean hasPermission = + mAtmService.instrumentationSourceHasPermission(pid, MODIFY_TOUCH_MODE_STATE) + || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()"); final long token = Binder.clearCallingIdentity(); try { if (mInputManager.setInTouchMode(mode, pid, uid, hasPermission)) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 044da3982e26..fe5f6759a17a 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -19,8 +19,6 @@ package com.android.server.wm; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.isStartResultSuccessful; import static android.view.Display.DEFAULT_DISPLAY; -import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION; -import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; @@ -681,7 +679,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } } - effects |= deleteTaskFragment(taskFragment, errorCallbackToken); + effects |= deleteTaskFragment(taskFragment, organizer, errorCallbackToken); break; } case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { @@ -699,8 +697,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub .startActivityInTaskFragment(tf, activityIntent, activityOptions, hop.getCallingActivity(), caller.mUid, caller.mPid); if (!isStartResultSuccessful(result)) { - sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), - errorCallbackToken, + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, convertStartFailureToThrowable(result, activityIntent)); } else { effects |= TRANSACT_EFFECTS_LIFECYCLE; @@ -710,13 +707,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { final IBinder fragmentToken = hop.getNewParent(); final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); - if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { + final TaskFragment parent = mLaunchTaskFragments.get(fragmentToken); + if (parent == null || activity == null) { final Throwable exception = new IllegalArgumentException( "Not allowed to operate with invalid fragment token or activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } - activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); + if (!parent.isAllowedToEmbedActivity(activity)) { + final Throwable exception = new SecurityException( + "The task fragment is not trusted to embed the given activity."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + activity.reparent(parent, POSITION_TOP); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } @@ -860,12 +864,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final WindowContainer newParent = hop.getNewParent() != null ? WindowContainer.fromBinder(hop.getNewParent()) : null; - if (oldParent == null || !oldParent.isAttached()) { + if (oldParent == null || oldParent.asTaskFragment() == null + || !oldParent.isAttached()) { Slog.e(TAG, "Attempt to operate on unknown or detached container: " + oldParent); break; } - reparentTaskFragment(oldParent, newParent, errorCallbackToken); + reparentTaskFragment(oldParent.asTaskFragment(), newParent, organizer, + errorCallbackToken); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } @@ -1246,7 +1252,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.enforceTaskPermission(func); } - private void enforceTaskPermission(String func, WindowContainerTransaction t) { + private void enforceTaskPermission(String func, @Nullable WindowContainerTransaction t) { if (t == null || t.getTaskFragmentOrganizer() == null) { enforceTaskPermission(func); return; @@ -1271,14 +1277,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); // Only allow to apply changes to TaskFragment that is created by this organizer. - WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); + final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); enforceTaskFragmentOrganized(func, wc, organizer); enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer); } - // TODO(b/197364677): Enforce safety of hierarchy operations in untrusted mode. E.g. one - // could first change a trusted TF, and then start/reparent untrusted activity there. - // Hierarchy changes final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); for (int i = hops.size() - 1; i >= 0; i--) { @@ -1352,8 +1355,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the * parent bounds are not allowed for embedding without full trust between the host and the * target. - * TODO(b/197364677): Allow SC transactions when the client-driven animations are protected from - * tapjacking. */ private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc, WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) { @@ -1361,35 +1362,48 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.e(TAG, "Attempt to operate on task fragment that no longer exists"); return; } - // Check if TaskFragment is embedded in fully trusted mode - if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) { - // Fully trusted, no need to check further - return; - } - if (change == null) { return; } final int changeMask = change.getChangeMask(); - if ((changeMask & (CHANGE_BOUNDS_TRANSACTION | CHANGE_BOUNDS_TRANSACTION_RECT)) != 0) { + if (changeMask != 0) { + // None of the change should be requested from a TaskFragment organizer. String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " trying to apply SurfaceControl changes to TaskFragment in non-trusted " - + "embedding mode, TaskFragmentOrganizer=" + organizer; + + " trying to apply changes of " + changeMask + " to TaskFragment" + + " TaskFragmentOrganizer=" + organizer; Slog.w(TAG, msg); throw new SecurityException(msg); } - if (change.getWindowSetMask() == 0) { - // Nothing else to check. + // Check if TaskFragment is embedded in fully trusted mode. + if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) { + // Fully trusted, no need to check further return; } - WindowConfiguration requestedWindowConfig = change.getConfiguration().windowConfiguration; - WindowContainer wcParent = wc.getParent(); + final WindowContainer wcParent = wc.getParent(); if (wcParent == null) { - Slog.e(TAG, "Attempt to set bounds on task fragment that has no parent"); + Slog.e(TAG, "Attempt to apply config change on task fragment that has no parent"); + return; + } + final Configuration requestedConfig = change.getConfiguration(); + final Configuration parentConfig = wcParent.getConfiguration(); + if (parentConfig.screenWidthDp < requestedConfig.screenWidthDp + || parentConfig.screenHeightDp < requestedConfig.screenHeightDp + || parentConfig.smallestScreenWidthDp < requestedConfig.smallestScreenWidthDp) { + String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply screen width/height greater than parent's for non-trusted" + + " host, TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + if (change.getWindowSetMask() == 0) { + // No bounds change. return; } - if (!wcParent.getBounds().contains(requestedWindowConfig.getBounds())) { + final WindowConfiguration requestedWindowConfig = requestedConfig.windowConfiguration; + final WindowConfiguration parentWindowConfig = parentConfig.windowConfiguration; + if (!parentWindowConfig.getBounds().contains(requestedWindowConfig.getBounds())) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply bounds outside of parent for non-trusted host," @@ -1397,6 +1411,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.w(TAG, msg); throw new SecurityException(msg); } + if (requestedWindowConfig.getAppBounds() != null + && parentWindowConfig.getAppBounds() != null + && !parentWindowConfig.getAppBounds().contains( + requestedWindowConfig.getAppBounds())) { + String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply app bounds outside of parent for non-trusted host," + + " TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } } void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, @@ -1422,7 +1447,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() || ownerActivity.getTask().effectiveUid != caller.mUid) { final Throwable exception = - new IllegalArgumentException("Not allowed to operate with the ownerToken while " + new SecurityException("Not allowed to operate with the ownerToken while " + "the root activity of the target task belong to the different app"); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; @@ -1439,33 +1464,46 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); } - void reparentTaskFragment(@NonNull WindowContainer oldParent, - @Nullable WindowContainer newParent, @Nullable IBinder errorCallbackToken) { - WindowContainer parent = newParent; - if (parent == null && oldParent.asTaskFragment() != null) { - parent = oldParent.asTaskFragment().getTask(); + void reparentTaskFragment(@NonNull TaskFragment oldParent, @Nullable WindowContainer newParent, + @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) { + final TaskFragment newParentTF; + if (newParent == null) { + // Use the old parent's parent if the caller doesn't specify the new parent. + newParentTF = oldParent.getTask(); + } else { + newParentTF = newParent.asTaskFragment(); } - if (parent == null) { + if (newParentTF == null) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with invalid container"); - sendTaskFragmentOperationFailure(oldParent.asTaskFragment().getTaskFragmentOrganizer(), - errorCallbackToken, exception); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } + if (newParentTF.getTaskFragmentOrganizer() != null) { + // We are reparenting activities to a new embedded TaskFragment, this operation is only + // allowed if the new parent is trusted by all reparent activities. + final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity -> + !newParentTF.isAllowedToEmbedActivity(activity)); + if (isEmbeddingDisallowed) { + final Throwable exception = new SecurityException( + "The new parent is not trusted to embed the activities."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + return; + } + } while (oldParent.hasChild()) { - oldParent.getChildAt(0).reparent(parent, POSITION_TOP); + oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP); } } private int deleteTaskFragment(@NonNull TaskFragment taskFragment, - @Nullable IBinder errorCallbackToken) { + @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) { final int index = mLaunchTaskFragments.indexOfValue(taskFragment); if (index < 0) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with invalid " + "taskFragment"); - sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(), - errorCallbackToken, exception); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return 0; } mLaunchTaskFragments.removeAt(index); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index bb7876734260..0ca1058a80b2 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -106,6 +106,7 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; @@ -140,7 +141,6 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.WINDOW; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER; @@ -252,6 +252,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.KeyInterceptionInfo; +import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ToBooleanFunction; @@ -262,6 +263,7 @@ import com.android.server.wm.SurfaceAnimator.AnimationType; import dalvik.annotation.optimization.NeverCompile; import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; @@ -2241,6 +2243,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Mark all relevant flags for that onExitAnimationDone will proceed all the way // to actually remove it. if (!visible && isVisibleNow && mActivityRecord.isAnimating(PARENTS | TRANSITION)) { + ProtoLog.d(WM_DEBUG_ANIM, + "Set animatingExit: reason=onAppVisibilityChanged win=%s", this); mAnimatingExit = true; mRemoveOnExit = true; mWindowRemovalAllowed = true; @@ -2580,6 +2584,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // been removed. We probably need another flag to indicate that window removal // should be deffered vs. overloading the flag that says we are playing an exit // animation. + ProtoLog.v(WM_DEBUG_ANIM, + "Set animatingExit: reason=remove/replaceWindow win=%s", this); mAnimatingExit = true; mReplacingRemoveRequested = true; return; @@ -2608,6 +2614,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Try starting an animation. if (mWinAnimator.applyAnimationLocked(transit, false)) { + ProtoLog.v(WM_DEBUG_ANIM, + "Set animatingExit: reason=remove/applyAnimation win=%s", this); mAnimatingExit = true; // mAnimatingExit affects canAffectSystemUiFlags(). Run layout such that @@ -2634,6 +2642,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // The exit animation is running or should run... wait for it! ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Not removing %s due to exit animation", this); + ProtoLog.v(WM_DEBUG_ANIM, "Set animatingExit: reason=remove/isAnimating win=%s", + this); setupWindowForRemoveOnExit(); if (mActivityRecord != null) { mActivityRecord.updateReportedVisibilityLocked(); @@ -3593,6 +3603,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Clear animating flags now, since the surface is now gone. (Note this is true even // if the surface is saved, to outside world the surface is still NO_SURFACE.) mAnimatingExit = false; + ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=destroySurface win=%s", this); if (useBLASTSync()) { immediatelyNotifyBlastSync(); @@ -3869,25 +3880,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outFrames.displayFrame.scale(mInvGlobalScale); } - final Rect backdropFrame = outFrames.backdropFrame; - // When the task is docked, we send fullscreen sized backdropFrame as soon as resizing - // start even if we haven't received the relayout window, so that the client requests - // the relayout sooner. When dragging stops, backdropFrame needs to stay fullscreen - // until the window to small size, otherwise the multithread renderer will shift last - // one or more frame to wrong offset. So here we send fullscreen backdrop if either - // isDragResizing() or isDragResizeChanged() is true. - final boolean resizing = isDragResizing() || isDragResizeChanged(); - if (!resizing || getWindowConfiguration().useWindowFrameForBackdrop()) { - // Surface position is now inherited from parent, and BackdropFrameRenderer uses - // backdrop frame to position content. Thus we just keep the size of backdrop frame, - // and remove the offset to avoid double offset from display origin. - backdropFrame.set(outFrames.frame); - backdropFrame.offsetTo(0, 0); - } else { - final DisplayInfo displayInfo = getDisplayInfo(); - backdropFrame.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); - } - // Note: in the cases where the window is tied to an activity, we should not send a // configuration update when the window has requested to be hidden. Doing so can lead to // the client erroneously accepting a configuration that would have otherwise caused an @@ -4682,8 +4674,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Force the show in the next prepareSurfaceLocked() call. mWinAnimator.mLastAlpha = -1; - if (DEBUG_ANIM) Slog.v(TAG, - "performShowLocked: mDrawState=HAS_DRAWN in " + this); + ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this); mWinAnimator.mDrawState = HAS_DRAWN; mWmService.scheduleAnimationLocked(); @@ -5003,7 +4994,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean isExitAnimationRunningSelfOrParent() { - return inAppOrRecentsTransition() + return inTransitionSelfOrParent() || isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION); } @@ -5011,6 +5002,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION); } + /** + * @return {@code true} if self or the parent container of the window is in transition. + * (e.g. The app or recents transition) + */ + boolean inTransitionSelfOrParent() { + if (!mTransitionController.isShellTransitionsEnabled()) { + return isAnimating(PARENTS | TRANSITION, + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); + } + return mTransitionController.inTransition(this); + } + private boolean shouldFinishAnimatingExit() { // Exit animation might be applied soon. if (inTransition()) { @@ -5054,9 +5057,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void onExitAnimationDone() { - if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this - + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit - + " selfAnimating=" + isAnimating()); + if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) { + final AnimationAdapter animationAdapter = mSurfaceAnimator.getAnimation(); + StringWriter sw = new StringWriter(); + if (animationAdapter != null) { + PrintWriter pw = new PrintWriter(sw); + animationAdapter.dump(pw, ""); + } + ProtoLog.v(WM_DEBUG_ANIM, "onExitAnimationDone in %s" + + ": exiting=%b remove=%b selfAnimating=%b anim=%s", + this, mAnimatingExit, mRemoveOnExit, isAnimating(), sw); + } if (!mChildren.isEmpty()) { // Copying to a different list as multiple children can be removed. @@ -5110,6 +5121,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } mAnimatingExit = false; + ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=exitAnimationDone win=%s", this); getDisplayContent().mWallpaperController.hideWallpapers(this); } @@ -5141,6 +5153,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // show up with wrong position or scale. if (mAnimatingExit) { mAnimatingExit = false; + ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=clearAnimatingFlags win=%s", + this); didSomething = true; } if (mDestroying) { @@ -5215,6 +5229,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP cancelAnimation(); } mAnimatingExit = false; + ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=relayoutVisibleWindow win=%s", + this); } if (mDestroying) { mDestroying = false; @@ -5299,7 +5315,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return; } - if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this); + ProtoLog.v(WM_DEBUG_ANIM, "Setting move animation on %s", this); final Point oldPosition = new Point(); final Point newPosition = new Point(); transformFrameToSurfacePosition(mWindowFrames.mLastFrame.left, mWindowFrames.mLastFrame.top, diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 285a6d5bdf5f..5c0557f2d1f4 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.TRANSIT_OLD_NONE; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DRAW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; @@ -34,7 +35,6 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMAT import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; @@ -60,6 +60,7 @@ import android.view.WindowManager.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; @@ -178,10 +179,9 @@ class WindowStateAnimator { void onAnimationFinished() { // Done animating, clean up. - if (DEBUG_ANIM) Slog.v( - TAG, "Animation done in " + this + ": exiting=" + mWin.mAnimatingExit - + ", reportedVisible=" - + (mWin.mActivityRecord != null && mWin.mActivityRecord.reportedVisible)); + ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s: exiting=%b, reportedVisible=%b", + this, mWin.mAnimatingExit, + (mWin.mActivityRecord != null && mWin.mActivityRecord.reportedVisible)); mWin.checkPolicyVisibilityChange(); final DisplayContent displayContent = mWin.getDisplayContent(); @@ -264,9 +264,8 @@ class WindowStateAnimator { if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) { return false; } - if (DEBUG_ANIM) { - Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceController); - } + ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s", + mSurfaceController); mDrawState = READY_TO_SHOW; boolean result = false; final ActivityRecord activity = mWin.mActivityRecord; @@ -298,9 +297,7 @@ class WindowStateAnimator { w.setHasSurface(false); - if (DEBUG_ANIM) { - Slog.i(TAG, "createSurface " + this + ": mDrawState=DRAW_PENDING"); - } + ProtoLog.i(WM_DEBUG_ANIM, "createSurface %s: mDrawState=DRAW_PENDING", this); resetDrawState(); @@ -503,8 +500,8 @@ class WindowStateAnimator { } } } else { - if (DEBUG_ANIM && mWin.isAnimating(TRANSITION | PARENTS)) { - Slog.v(TAG, "prepareSurface: No changes in animation for " + this); + if (mWin.isAnimating(TRANSITION | PARENTS)) { + ProtoLog.v(WM_DEBUG_ANIM, "prepareSurface: No changes in animation for %s", this); } } @@ -646,15 +643,12 @@ class WindowStateAnimator { mWin.mAttrs, attr, TRANSIT_OLD_NONE); } } - if (DEBUG_ANIM) Slog.v(TAG, - "applyAnimation: win=" + this - + " anim=" + anim + " attr=0x" + Integer.toHexString(attr) - + " a=" + a - + " transit=" + transit - + " type=" + mAttrType - + " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3)); + if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) { + ProtoLog.v(WM_DEBUG_ANIM, "applyAnimation: win=%s" + + " anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s", + this, anim, attr, a, transit, mAttrType, isEntrance, Debug.getCallers(20)); + } if (a != null) { - if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this); Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation"); mWin.startAnimation(a); Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java index bf78c67d5b57..82fe8b9362b0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java @@ -16,15 +16,12 @@ package com.android.server.devicepolicy; -import static android.app.admin.DevicePolicyResources.Drawables.Style; - import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicyResources; -import android.app.admin.DevicePolicyResources.Drawables; import android.app.admin.DevicePolicyStringResource; import android.app.admin.ParcelableResource; import android.os.Environment; @@ -180,16 +177,18 @@ class DeviceManagementResourcesProvider { @Nullable ParcelableResource getDrawable( String drawableId, String drawableStyle, String drawableSource) { - if (mUpdatedDrawablesForSource.containsKey(drawableId) - && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) { - return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource); - } - if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { - Log.d(TAG, "No updated drawable found for drawable id " + drawableId); - return null; - } - if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(drawableStyle)) { - return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle); + synchronized (mLock) { + if (mUpdatedDrawablesForSource.containsKey(drawableId) + && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) { + return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource); + } + if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { + Log.d(TAG, "No updated drawable found for drawable id " + drawableId); + return null; + } + if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(drawableStyle)) { + return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle); + } } Log.d(TAG, "No updated drawable found for drawable id " + drawableId); return null; @@ -249,10 +248,11 @@ class DeviceManagementResourcesProvider { @Nullable ParcelableResource getString(String stringId) { - if (mUpdatedStrings.containsKey(stringId)) { - return mUpdatedStrings.get(stringId); + synchronized (mLock) { + if (mUpdatedStrings.containsKey(stringId)) { + return mUpdatedStrings.get(stringId); + } } - Log.d(TAG, "No updated string found for string id " + stringId); return null; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 1052d1dead87..c5bb1bfb4663 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -114,6 +114,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHAN import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE; import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_SOON_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE; import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE; import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN; @@ -172,7 +174,6 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; -import android.app.ActivityThread; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -7030,14 +7031,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private String getGenericWipeReason( boolean calledByProfileOwnerOnOrgOwnedDevice, boolean calledOnParentInstance) { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); return calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance - ? dpm.getResources().getString(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE, - () -> mContext.getString( - R.string.device_ownership_relinquished)) - : dpm.getResources().getString(WORK_PROFILE_DELETED_GENERIC_MESSAGE, - () -> mContext.getString( - R.string.work_profile_deleted_description_dpm_wipe)); + ? getUpdatableString( + WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE, + R.string.device_ownership_relinquished) + : getUpdatableString( + WORK_PROFILE_DELETED_GENERIC_MESSAGE, + R.string.work_profile_deleted_description_dpm_wipe); } /** @@ -7133,9 +7133,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private String getWorkProfileDeletedTitle() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(WORK_PROFILE_DELETED_TITLE, - () -> mContext.getString(R.string.work_profile_deleted)); + return getUpdatableString(WORK_PROFILE_DELETED_TITLE, R.string.work_profile_deleted); } private void clearWipeProfileNotification() { @@ -7449,10 +7447,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private String getFailedPasswordAttemptWipeMessage() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE, - () -> mContext.getString( - R.string.work_profile_deleted_reason_maximum_password_failure)); + return getUpdatableString( + WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE, + R.string.work_profile_deleted_reason_maximum_password_failure); } /** @@ -12649,15 +12646,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private String getLocationChangedTitle() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(LOCATION_CHANGED_TITLE, - () -> mContext.getString(R.string.location_changed_notification_title)); + return getUpdatableString( + LOCATION_CHANGED_TITLE, R.string.location_changed_notification_title); } private String getLocationChangedText() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(LOCATION_CHANGED_MESSAGE, - () -> mContext.getString(R.string.location_changed_notification_text)); + return getUpdatableString( + LOCATION_CHANGED_MESSAGE, R.string.location_changed_notification_text); } @Override @@ -13256,19 +13251,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Slogf.e(LOG_TAG, "appLabel is inexplicably null"); return null; } - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString( + return getUpdatableString( PRINTING_DISABLED_NAMED_ADMIN, - () -> getDefaultPrintingDisabledMsg(appLabel), + R.string.printing_disabled_by, appLabel); } } - private String getDefaultPrintingDisabledMsg(CharSequence appLabel) { - return ((Context) ActivityThread.currentActivityThread().getSystemUiContext()) - .getResources().getString(R.string.printing_disabled_by, appLabel); - } - @Override protected DevicePolicyCache getDevicePolicyCache() { return mPolicyCache; @@ -15875,15 +15864,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private String getNetworkLoggingTitle() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(NETWORK_LOGGING_TITLE, - () -> mContext.getString(R.string.network_logging_notification_title)); + return getUpdatableString( + NETWORK_LOGGING_TITLE, R.string.network_logging_notification_title); } private String getNetworkLoggingText() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(NETWORK_LOGGING_MESSAGE, - () -> mContext.getString(R.string.network_logging_notification_text)); + return getUpdatableString( + NETWORK_LOGGING_MESSAGE, R.string.network_logging_notification_text); } private void handleCancelNetworkLoggingNotification() { @@ -17470,35 +17457,31 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private String getPersonalAppSuspensionButtonText() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE, - () -> mContext.getString(R.string.personal_apps_suspended_turn_profile_on)); + return getUpdatableString( + PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE, + R.string.personal_apps_suspended_turn_profile_on); } private String getPersonalAppSuspensionTitle() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TITLE, - () -> mContext.getString(R.string.personal_apps_suspension_title)); + return getUpdatableString( + PERSONAL_APP_SUSPENSION_TITLE, R.string.personal_apps_suspension_title); } private String getPersonalAppSuspensionText() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TITLE, - () -> mContext.getString(R.string.personal_apps_suspension_text)); + return getUpdatableString( + PERSONAL_APP_SUSPENSION_MESSAGE, R.string.personal_apps_suspension_text); } private String getPersonalAppSuspensionSoonText(String date, String time, int maxDays) { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TITLE, - () -> mContext.getString( - R.string.personal_apps_suspension_soon_text, date, time, maxDays), + return getUpdatableString( + PERSONAL_APP_SUSPENSION_SOON_MESSAGE, R.string.personal_apps_suspension_soon_text, date, time, maxDays); } private String getWorkProfileContentDescription() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, - () -> mContext.getString(R.string.notification_work_profile_content_description)); + return getUpdatableString( + NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, + R.string.notification_work_profile_content_description); } @Override @@ -18802,6 +18785,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private String getUpdatableString( + String updatableStringId, int defaultStringId, Object... formatArgs) { + ParcelableResource resource = mDeviceManagementResourcesProvider.getString( + updatableStringId); + if (resource == null) { + return ParcelableResource.loadDefaultString(() -> + mContext.getString(defaultStringId, formatArgs)); + } + return resource.getString( + mContext, () -> mContext.getString(defaultStringId, formatArgs), formatArgs); + } + public boolean isDpcDownloaded() { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); @@ -18842,14 +18837,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); - int userId = user.getIdentifier(); return mInjector.binderWithCleanCallingIdentity(() -> { List<UserInfo> userProfiles = mUserManager.getProfiles(userId); List<UserHandle> result = new ArrayList<>(); for (int i = 0; i < userProfiles.size(); i++) { - if (userProfiles.get(i).isManagedProfile() && hasProfileOwner(userId)) { - result.add(new UserHandle(userProfiles.get(i).id)); + UserInfo userInfo = userProfiles.get(i); + if (userInfo.isManagedProfile() && hasProfileOwner(userInfo.id)) { + result.add(new UserHandle(userInfo.id)); } } return result; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 0e96567dbfad..a49577b21957 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -111,6 +111,11 @@ static bool getAlwaysEnableReadTimeoutsForSystemDataLoaders() { true); } +static bool getEnableReadTimeoutsAfterInstall() { + return android::base::GetBoolProperty("debug.incremental.enable_read_timeouts_after_install", + true); +} + static bool getEnforceReadLogsMaxIntervalForSystemDataLoaders() { return android::base::GetBoolProperty("debug.incremental.enforce_readlogs_max_interval_for_" "system_dataloaders", @@ -853,7 +858,7 @@ void IncrementalService::onInstallationComplete(StorageId storage) { // Always enable long read timeouts after installation is complete. std::unique_lock l(ifs->lock); - ifs->setReadTimeoutsRequested(true); + ifs->setReadTimeoutsRequested(getEnableReadTimeoutsAfterInstall()); applyStorageParamsLocked(*ifs); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f7c66c5cff69..73f0a74c232e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1486,8 +1486,9 @@ public final class SystemServer implements Dumpable { // TelecomLoader hooks into classes with defined HFP logic, // so check for either telephony or microphone. - if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) || - mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) + || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELECOM) + || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { t.traceBegin("StartTelecomLoaderService"); mSystemServiceManager.startService(TelecomLoaderService.class); t.traceEnd(); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index ca67bcb7f38c..e1fe1d8433ef 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -18,6 +18,7 @@ package com.android.server.midi; import android.annotation.NonNull; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -151,6 +152,8 @@ public class MidiService extends IMidiManager.Stub { private static final UUID MIDI_SERVICE = UUID.fromString( "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); + private final HashSet<ParcelUuid> mNonMidiUUIDs = new HashSet<ParcelUuid>(); + // PackageMonitor for listening to package changes private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override @@ -643,6 +646,55 @@ public class MidiService extends IMidiManager.Stub { return false; } + private static void dumpIntentExtras(Intent intent) { + String action = intent.getAction(); + Log.d(TAG, "Intent: " + action); + Bundle bundle = intent.getExtras(); + if (bundle != null) { + for (String key : bundle.keySet()) { + Log.d(TAG, " " + key + " : " + + (bundle.get(key) != null ? bundle.get(key) : "NULL")); + } + } + } + + private static boolean isBleTransport(Intent intent) { + Bundle bundle = intent.getExtras(); + boolean isBle = false; + if (bundle != null) { + isBle = bundle.getInt(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_AUTO) + == BluetoothDevice.TRANSPORT_LE; + } + return isBle; + } + + private void dumpUuids(BluetoothDevice btDevice) { + Log.d(TAG, "UUIDs for " + btDevice); + + ParcelUuid[] uuidParcels = btDevice.getUuids(); + if (uuidParcels == null) { + Log.d(TAG, "No UUID Parcels"); + return; + } + + for (ParcelUuid parcel : uuidParcels) { + UUID uuid = parcel.getUuid(); + Log.d(TAG, " uuid:" + uuid); + } + } + + private boolean hasNonMidiUuids(BluetoothDevice btDevice) { + ParcelUuid[] uuidParcels = btDevice.getUuids(); + // The assumption is that these services are indicative of devices that + // ARE NOT MIDI devices. + for (ParcelUuid parcel : uuidParcels) { + if (mNonMidiUUIDs.contains(parcel)) { + return true; + } + } + return false; + } + private final BroadcastReceiver mBleMidiReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -655,10 +707,27 @@ public class MidiService extends IMidiManager.Stub { switch (action) { case BluetoothDevice.ACTION_ACL_CONNECTED: { Log.d(TAG, "ACTION_ACL_CONNECTED"); + dumpIntentExtras(intent); + // BLE-MIDI controllers are by definition BLE, so if this device + // isn't, it CAN'T be a midi device + if (!isBleTransport(intent)) { + Log.i(TAG, "No BLE transport - NOT MIDI"); + break; + } + + Log.d(TAG, "BLE Device"); BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - // We can't determine here if this is a BLD-MIDI device, so go ahead and try - // to open as a MIDI device, further down it will get figured out. + dumpUuids(btDevice); + + // See if there are any service UUIDs and if so do any of them indicate a + // Non-MIDI device (headset, headphones, QWERTY keyboard....) + if (hasNonMidiUuids(btDevice)) { + Log.d(TAG, "Non-MIDI service UUIDs found. NOT MIDI"); + break; + } + + Log.d(TAG, "Potential MIDI Device."); openBluetoothDevice(btDevice); } break; @@ -689,6 +758,17 @@ public class MidiService extends IMidiManager.Stub { context.registerReceiver(mBleMidiReceiver, filter); mBluetoothServiceUid = -1; + + mNonMidiUUIDs.add(BluetoothUuid.A2DP_SINK); // Headphones? + mNonMidiUUIDs.add(BluetoothUuid.A2DP_SOURCE); // Headset? + mNonMidiUUIDs.add(BluetoothUuid.ADV_AUDIO_DIST); + mNonMidiUUIDs.add(BluetoothUuid.AVRCP_CONTROLLER); + mNonMidiUUIDs.add(BluetoothUuid.HFP); + mNonMidiUUIDs.add(BluetoothUuid.HSP); + mNonMidiUUIDs.add(BluetoothUuid.HID); + mNonMidiUUIDs.add(BluetoothUuid.LE_AUDIO); + mNonMidiUUIDs.add(BluetoothUuid.HOGP); + mNonMidiUUIDs.add(BluetoothUuid.HEARING_AID); } private void onUnlockUser() { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index d44fb3bc2448..47d4c8d9af46 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -19,6 +19,8 @@ package com.android.server.am; import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.CAMERA; +import static android.Manifest.permission.RECORD_AUDIO; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; @@ -26,6 +28,14 @@ import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTE import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.isLowRamDeviceStatic; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_ACTIVATE_PLATFORM_VPN; +import static android.app.AppOpsManager.OP_ACTIVATE_VPN; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_FINE_LOCATION; +import static android.app.AppOpsManager.OP_NONE; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; @@ -301,21 +311,21 @@ public final class BackgroundRestrictionTest { doReturn(new String[]{packageName}) .when(mPackageManager) .getPackagesForUid(eq(uid)); - doReturn(AppOpsManager.MODE_IGNORED) - .when(mAppOpsManager) - .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, uid, packageName); - doReturn(AppOpsManager.MODE_IGNORED) - .when(mAppOpsManager) - .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, uid, packageName); + final int[] ops = new int[] { + OP_ACTIVATE_VPN, + OP_ACTIVATE_PLATFORM_VPN, + OP_FINE_LOCATION, + OP_CAMERA, + OP_RECORD_AUDIO, + }; + for (int op : ops) { + setAppOpState(packageName, uid, op, false); + } final String[] permissions = new String[] {ACCESS_BACKGROUND_LOCATION, - ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}; + ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, CAMERA, RECORD_AUDIO, + }; for (String permission : permissions) { - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, permission); - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkPermission(packageName, permission, userId); + setPermissionState(packageName, uid, permission, false); } } doReturn(appStandbyInfoList).when(mAppStandbyInternal).getAppStandbyBuckets(userId); @@ -1145,51 +1155,51 @@ public final class BackgroundRestrictionTest { // Long-running FGS with type "location", but ran for a very short time. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_LOCATION, 0, null, null, null, + FOREGROUND_SERVICE_TYPE_LOCATION, 0, null, OP_NONE, null, null, timeout(windowMs * 2).times(2)); // Long-running FGS with type "location", and ran for a while. // We shouldn't see notifications in this case. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_LOCATION, thresholdMs * 2, null, null, null, + FOREGROUND_SERVICE_TYPE_LOCATION, thresholdMs * 2, null, OP_NONE, null, null, timeout(windowMs * 2).times(0)); // Long-running FGS with background location permission. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_LOCATION, 0, ACCESS_BACKGROUND_LOCATION, null, null, - timeout(windowMs * 2).times(0)); + FOREGROUND_SERVICE_TYPE_LOCATION, 0, ACCESS_BACKGROUND_LOCATION, OP_NONE, + null, null, timeout(windowMs * 2).times(0)); // Long-running FGS with type "mediaPlayback", but ran for a very short time. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, null, null, null, + FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, null, OP_NONE, null, null, timeout(windowMs * 2).times(2)); // Long-running FGS with type "mediaPlayback", and ran for a while. // We shouldn't see notifications in this case. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, thresholdMs * 2, null, null, null, - timeout(windowMs * 2).times(0)); + FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, thresholdMs * 2, null, OP_NONE, + null, null, timeout(windowMs * 2).times(0)); // Long-running FGS with type "camera", and ran for a while. // We shouldn't see notifications in this case. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_CAMERA, thresholdMs * 2, null, null, null, + FOREGROUND_SERVICE_TYPE_CAMERA, thresholdMs * 2, null, OP_NONE, null, null, timeout(windowMs * 2).times(0)); // Long-running FGS with type "location|mediaPlayback", but ran for a very short time. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, - 0, null, null, null, timeout(windowMs * 2).times(2)); + 0, null, OP_NONE, null, null, timeout(windowMs * 2).times(2)); // Long-running FGS with type "location|mediaPlayback", and ran for a while. // We shouldn't see notifications in this case. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, - thresholdMs * 2, null, null, null, timeout(windowMs * 2).times(0)); + thresholdMs * 2, null, OP_NONE, null, null, timeout(windowMs * 2).times(0)); // Long-running FGS with a media session starts/stops right away. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_NONE, 0, null, + FOREGROUND_SERVICE_TYPE_NONE, 0, null, OP_NONE, List.of(Pair.create(createMediaControllers( new String[] {testPkgName1}, new int[] {testUid1}), 0L)), null, timeout(windowMs * 2).times(2)); @@ -1197,14 +1207,14 @@ public final class BackgroundRestrictionTest { // Long-running FGS with media session, and ran for a while. // We shouldn't see notifications in this case. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_NONE, thresholdMs * 2, null, + FOREGROUND_SERVICE_TYPE_NONE, thresholdMs * 2, null, OP_NONE, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, new int[] {testUid1}), thresholdMs * 2)), null, timeout(windowMs * 2).times(0)); // Long-running FGS with 2 media sessions start/stop right away runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_NONE, 0, null, + FOREGROUND_SERVICE_TYPE_NONE, 0, null, OP_NONE, List.of(Pair.create(createMediaControllers( new String[] {testPkgName1, testPkgName2}, new int[] {testUid1, testUid2}), 0L)), null, @@ -1212,7 +1222,7 @@ public final class BackgroundRestrictionTest { // Long-running FGS with 2 media sessions start/stop interlaced. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_NONE, 0, null, + FOREGROUND_SERVICE_TYPE_NONE, 0, null, OP_NONE, List.of(Pair.create(createMediaControllers( new String[] {testPkgName1, testPkgName2}, new int[] {testUid1, testUid2}), thresholdMs), @@ -1230,17 +1240,17 @@ public final class BackgroundRestrictionTest { // Long-running FGS with top state for a very short time. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_NONE, 0, null, null, List.of(0L), + FOREGROUND_SERVICE_TYPE_NONE, 0, null, OP_NONE, null, List.of(0L), timeout(windowMs * 2).times(2)); // Long-running FGS with top state for extended time. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_NONE, 0, null, null, List.of(0L, windowMs * 2, 0L), - timeout(windowMs * 2).times(0)); + FOREGROUND_SERVICE_TYPE_NONE, 0, null, OP_NONE, null, + List.of(0L, windowMs * 2, 0L), timeout(windowMs * 2).times(0)); // Long-running FGS with top state, on and off frequently. runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1, - FOREGROUND_SERVICE_TYPE_NONE, 0, null, null, + FOREGROUND_SERVICE_TYPE_NONE, 0, null, OP_NONE, null, List.of(0L, thresholdMs / 10, thresholdMs / 10, thresholdMs / 10, thresholdMs / 10, thresholdMs / 10, thresholdMs / 10), timeout(windowMs * 2).times(2)); @@ -1259,19 +1269,19 @@ public final class BackgroundRestrictionTest { } private void runTestLongFGSExemptionOnce(String packageName, int uid, int pid, - int serviceType, long sleepMs, String perm, + int serviceType, long sleepMs, String perm, int op, List<Pair<List<MediaController>, Long>> mediaControllers, List<Long> topStateChanges, VerificationMode mode) throws Exception { runExemptionTestOnce( - packageName, uid, pid, serviceType, sleepMs, true, false, perm, mediaControllers, - topStateChanges, true, true, + packageName, uid, pid, serviceType, sleepMs, true, false, perm, op, + mediaControllers, topStateChanges, true, true, () -> checkNotificationShown(new String[] {packageName}, mode, false) ); } private void runExemptionTestOnce(String packageName, int uid, int pid, int serviceType, long sleepMs, boolean stopAfterSleep, - boolean withNotification, String perm, + boolean withNotification, String perm, int op, List<Pair<List<MediaController>, Long>> mediaControllers, List<Long> topStateChanges, boolean resetFGSTracker, boolean resetController, RunnableWithException r) throws Exception { @@ -1331,12 +1341,10 @@ public final class BackgroundRestrictionTest { } } if (perm != null) { - doReturn(PERMISSION_GRANTED) - .when(mPermissionManagerServiceInternal) - .checkPermission(packageName, perm, UserHandle.getUserId(uid)); - doReturn(PERMISSION_GRANTED) - .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, perm); + setPermissionState(packageName, uid, perm, true); + if (op != OP_NONE) { + setAppOpState(packageName, uid, op, true); + } mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } @@ -1357,12 +1365,10 @@ public final class BackgroundRestrictionTest { mAppFGSTracker.onForegroundServiceStateChanged(packageName, uid, pid, false); if (perm != null) { - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkPermission(packageName, perm, UserHandle.getUserId(uid)); - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, perm); + setPermissionState(packageName, uid, perm, false); + if (op != OP_NONE) { + setAppOpState(packageName, uid, op, false); + } mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } if (topStateThread != null) { @@ -1575,7 +1581,7 @@ public final class BackgroundRestrictionTest { // goto the restricted bucket. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true, false, - null, null, null, listener, stats, uids, + null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1584,7 +1590,7 @@ public final class BackgroundRestrictionTest { // Run with a media playback service with extended time. We should be back to normal. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false, - false, null, null, null, listener, stats, uids, + false, null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false, @@ -1613,7 +1619,7 @@ public final class BackgroundRestrictionTest { // Run with a media playback service with extended time, with higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false, - false, null, null, null, listener, stats, uids, + false, null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1622,7 +1628,7 @@ public final class BackgroundRestrictionTest { // Run with a media playback service with extended time, with even higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false, - false, null, null, null, listener, stats, uids, + false, null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, @@ -1636,8 +1642,9 @@ public final class BackgroundRestrictionTest { // Run with a media session with extended time, with higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false, - null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, - new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), + null, OP_NONE, + List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, + new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, @@ -1647,8 +1654,9 @@ public final class BackgroundRestrictionTest { // Run with a media session with extended time, with even higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false, - null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, - new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), + null, OP_NONE, + List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, + new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, @@ -1664,8 +1672,9 @@ public final class BackgroundRestrictionTest { // but it ran on the top when the location service is active. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false, - null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, - new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), + null, OP_NONE, + List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, + new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), List.of(0L, timeout * 2), listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, @@ -1680,7 +1689,7 @@ public final class BackgroundRestrictionTest { // Run with a location service with extended time, with higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false, - null, null, null, listener, stats, uids, + null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1689,7 +1698,7 @@ public final class BackgroundRestrictionTest { // Run with a location service with extended time, with even higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false, - null, null, null, listener, stats, uids, + null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, @@ -1704,7 +1713,7 @@ public final class BackgroundRestrictionTest { // but it ran on the top when the location service is active. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false, - null, null, List.of(0L, timeout * 2), listener, stats, uids, + null, OP_NONE, null, List.of(0L, timeout * 2), listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1721,7 +1730,7 @@ public final class BackgroundRestrictionTest { // Run with bg location permission, with moderate current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, false, - ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids, + ACCESS_BACKGROUND_LOCATION, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1730,7 +1739,7 @@ public final class BackgroundRestrictionTest { // Run with bg location permission, with a bit higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, false, - ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids, + ACCESS_BACKGROUND_LOCATION, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1747,7 +1756,7 @@ public final class BackgroundRestrictionTest { // Run with bg location permission, with higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, false, - ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids, + ACCESS_BACKGROUND_LOCATION, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true , RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, @@ -1756,7 +1765,7 @@ public final class BackgroundRestrictionTest { // Run with bg location permission, with even higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, false, - ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids, + ACCESS_BACKGROUND_LOCATION, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, @@ -1778,7 +1787,7 @@ public final class BackgroundRestrictionTest { // goto the restricted bucket. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true, false, - null, null, null, listener, stats, uids, + null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1787,7 +1796,7 @@ public final class BackgroundRestrictionTest { // Run with a media playback service with extended time. We should be back to normal. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false, - false, null, null, null, listener, stats, uids, + false, null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false, @@ -1819,7 +1828,7 @@ public final class BackgroundRestrictionTest { // Run with a media playback service with extended time, with higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false, - false, null, null, null, listener, stats, uids, + false, null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1830,7 +1839,7 @@ public final class BackgroundRestrictionTest { // playback. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false, - false, null, null, null, listener, stats, uids, + false, null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah + 100, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, @@ -1846,7 +1855,7 @@ public final class BackgroundRestrictionTest { // Run with coarse location permission, with high current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, false, - ACCESS_COARSE_LOCATION, null, null, listener, stats, uids, + ACCESS_COARSE_LOCATION, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1860,7 +1869,7 @@ public final class BackgroundRestrictionTest { // Run with fine location permission, with high current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, false, - ACCESS_FINE_LOCATION, null, null, listener, stats, uids, + ACCESS_FINE_LOCATION, OP_FINE_LOCATION, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1874,8 +1883,9 @@ public final class BackgroundRestrictionTest { // Run with a media session with extended time, with higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false, - null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, - new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), + null, OP_NONE, + List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, + new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, @@ -1887,8 +1897,9 @@ public final class BackgroundRestrictionTest { // session. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false, - null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, - new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), + null, OP_NONE, + List.of(Pair.create(createMediaControllers(new String[] {testPkgName1}, + new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)), null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah + 100, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, @@ -1906,7 +1917,7 @@ public final class BackgroundRestrictionTest { // goto the restricted bucket. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, true, true, - null, null, null, listener, stats, uids, + null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1915,7 +1926,7 @@ public final class BackgroundRestrictionTest { // Run with a service with notification for extended time. We should be back to normal. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, - true, null, null, null, listener, stats, uids, + true, null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketThresholdMah + 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false, @@ -1949,7 +1960,7 @@ public final class BackgroundRestrictionTest { // Run with a location service with extended time, with higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false, - null, null, null, listener, stats, uids, + null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, @@ -1959,7 +1970,7 @@ public final class BackgroundRestrictionTest { // it still should stay in the current restriction level as we exempt the location. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false, - null, null, null, listener, stats, uids, + null, OP_NONE, null, null, listener, stats, uids, new double[]{restrictBucketHighThresholdMah + 100, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, @@ -1984,7 +1995,7 @@ public final class BackgroundRestrictionTest { private void runTestBgCurrentDrainExemptionOnce(String packageName, int uid, int pid, int serviceType, long sleepMs, boolean stopAfterSleep, boolean withNotification, - String perm, List<Pair<List<MediaController>, Long>> mediaControllers, + String perm, int op, List<Pair<List<MediaController>, Long>> mediaControllers, List<Long> topStateChanges, TestAppRestrictionLevelListener listener, BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg, double[] cached, boolean expectingTimeout, int expectingLevel, long timeout, @@ -2001,18 +2012,16 @@ public final class BackgroundRestrictionTest { mAppBatteryPolicy.reset(); } if (perm != null) { - doReturn(PERMISSION_GRANTED) - .when(mPermissionManagerServiceInternal) - .checkPermission(packageName, perm, UserHandle.getUserId(uid)); - doReturn(PERMISSION_GRANTED) - .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, perm); + setPermissionState(packageName, uid, perm, true); + if (op != OP_NONE) { + setAppOpState(packageName, uid, op, true); + } mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } waitForIdleHandler(mBgRestrictionController.getBackgroundHandler()); runExemptionTestOnce( packageName, uid, pid, serviceType, sleepMs, stopAfterSleep, withNotification, - perm, mediaControllers, topStateChanges, resetFGSTracker, false, + perm, op, mediaControllers, topStateChanges, resetFGSTracker, false, () -> { clearInvocations(mInjector.getAppStandbyInternal()); clearInvocations(mBgRestrictionController); @@ -2060,16 +2069,36 @@ public final class BackgroundRestrictionTest { } ); if (perm != null) { - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkPermission(packageName, perm, UserHandle.getUserId(uid)); - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, perm); + setPermissionState(packageName, uid, perm, false); + if (op != OP_NONE) { + setAppOpState(packageName, uid, op, false); + } mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } } + private void setPermissionState(String packageName, int uid, String perm, boolean granted) { + doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkUidPermission(uid, perm); + doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkPermission(packageName, perm, UserHandle.getUserId(uid)); + } + + private void setAppOpState(String packageName, int uid, int op, boolean granted) { + try { + doReturn(granted ? MODE_ALLOWED : MODE_IGNORED) + .when(mAppOpsManager) + .checkOpNoThrow(op, uid, packageName); + doReturn(granted ? MODE_ALLOWED : MODE_IGNORED) + .when(mIAppOpsService) + .checkOperation(op, uid, packageName); + } catch (RemoteException e) { + // Ignore. + } + } + @Test public void testExcessiveBroadcasts() throws Exception { final long windowMs = 5_000; diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index a6194df18217..449177ef9b7d 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -100,7 +100,7 @@ android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> <uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" /> <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" /> - + <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" /> <queries> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index 46515468489d..edacc165016f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -16,8 +16,6 @@ package com.android.server.accessibility; -import static android.content.pm.PackageManagerInternal.PACKAGE_INSTALLER; - import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static junit.framework.Assert.assertFalse; @@ -45,14 +43,10 @@ import android.appwidget.AppWidgetManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.InstallSourceInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.SigningInfo; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; @@ -63,7 +57,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.R; -import com.android.server.LocalServices; import org.junit.Before; import org.junit.Rule; @@ -150,22 +143,12 @@ public class AccessibilitySecurityPolicyTest { @Mock private AccessibilityServiceInfo mMockA11yServiceInfo; @Mock - private ResolveInfo mMockResolveInfo; - @Mock - private ServiceInfo mMockServiceInfo; - @Mock - private ApplicationInfo mMockApplicationInfo; - @Mock - private ApplicationInfo mMockSourceApplicationInfo; - @Mock - private PackageInfo mMockSourcePackageInfo; - @Mock private PolicyWarningUIController mPolicyWarningUIController; @Mock private PackageManagerInternal mPackageManagerInternal; @Before - public void setUp() throws PackageManager.NameNotFoundException { + public void setUp() { MockitoAnnotations.initMocks(this); mContext.setMockPackageManager(mMockPackageManager); mContext.addMockSystemService(Context.USER_SERVICE, mMockUserManager); @@ -612,8 +595,7 @@ public class AccessibilitySecurityPolicyTest { } @Test - public void onBoundServicesChanged_nonA11yTool_invokeAction() - throws PackageManager.NameNotFoundException { + public void onBoundServicesChanged_nonA11yTool_invokeAction() { final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>(); boundServices.add(mMockA11yServiceConnection); initServiceInfoAndConnection(TEST_COMPONENT_NAME, @@ -630,40 +612,11 @@ public class AccessibilitySecurityPolicyTest { } @Test - public void onBoundServicesChanged_sysA11yTool_noAction() - throws PackageManager.NameNotFoundException { - final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>(); - initServiceInfoAndConnection(TEST_COMPONENT_NAME, - mMockA11yServiceConnection, - /* isAccessibilityTool= */ true, - /* isSystemApp= */true, - /* installSourceInfo= */ null); - boundServices.add(mMockA11yServiceConnection); - - mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices); - verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceBound(anyInt(), any()); - - mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>()); - verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceUnbound(anyInt(), - any()); - } - - @Test - public void onBoundServicesChanged_nonSysA11yToolFromAllowedInstallerInAllowedList_noAction() - throws PackageManager.NameNotFoundException { + public void onBoundServicesChanged_isA11yTool_noAction() { final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>(); - final String allowedSourcePackageName = "com.allowed.install.package"; - mContext.getOrCreateTestableResources().addOverride(R.array - .config_accessibility_allowed_install_source, - new String[]{allowedSourcePackageName}); - // The allowed Installer should be system app in the allowed list. - InstallSourceInfo allowedSource = initInstallSourceInfo( - allowedSourcePackageName, /* isSystemApp= */ true); initServiceInfoAndConnection(TEST_COMPONENT_NAME, mMockA11yServiceConnection, - /* isAccessibilityTool= */ true, - /* isSystemApp= */ false, - allowedSource); + /* isAccessibilityTool= */ true); boundServices.add(mMockA11yServiceConnection); mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices); @@ -675,37 +628,7 @@ public class AccessibilitySecurityPolicyTest { } @Test - public void onBoundServicesChanged_nonSysA11yToolFromValidInstallerWithoutAllowedList_noAction() - throws PackageManager.NameNotFoundException { - final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>(); - final String validInstallerPackageName = "com.valid.install.package"; - final String defaultInstallerPackageName = "com.default.install.package"; - LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); - when(mPackageManagerInternal.getKnownPackageNames(PACKAGE_INSTALLER, - TEST_USER_ID)).thenReturn(new String[]{defaultInstallerPackageName}); - mContext.getOrCreateTestableResources().addOverride(R.array - .config_accessibility_allowed_install_source, - new String[]{}); - // The valid Installer should be system app and not the default installer. - InstallSourceInfo validSource = initInstallSourceInfo( - validInstallerPackageName, /* isSystemApp= */ true); - initServiceInfoAndConnection(TEST_COMPONENT_NAME, - mMockA11yServiceConnection, /* isAccessibilityTool= */ true, - /* isSystemApp= */ false, - validSource); - boundServices.add(mMockA11yServiceConnection); - - mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices); - verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceBound(anyInt(), any()); - - mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>()); - verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceUnbound(anyInt(), - any()); - } - - @Test - public void onSwitchUser_oldUserHadAction_invokeActionForOldUser() - throws PackageManager.NameNotFoundException { + public void onSwitchUser_oldUserHadAction_invokeActionForOldUser() { final int newUserId = 2; final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>(); initServiceInfoAndConnection(TEST_COMPONENT_NAME, @@ -725,35 +648,9 @@ public class AccessibilitySecurityPolicyTest { private void initServiceInfoAndConnection(ComponentName componentName, AccessibilityServiceConnection connection, - boolean isAccessibilityTool) throws PackageManager.NameNotFoundException { - initServiceInfoAndConnection(componentName, connection, isAccessibilityTool, false, null); - } - - private void initServiceInfoAndConnection(ComponentName componentName, - AccessibilityServiceConnection connection, - boolean isAccessibilityTool, boolean isSystemApp, InstallSourceInfo installSourceInfo) - throws PackageManager.NameNotFoundException { + boolean isAccessibilityTool) { when(connection.getServiceInfo()).thenReturn(mMockA11yServiceInfo); when(mMockA11yServiceInfo.getComponentName()).thenReturn(componentName); when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(isAccessibilityTool); - when(mMockA11yServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); - mMockResolveInfo.serviceInfo = mMockServiceInfo; - mMockServiceInfo.applicationInfo = mMockApplicationInfo; - mMockServiceInfo.packageName = componentName.getPackageName(); - when(mMockApplicationInfo.isSystemApp()).thenReturn(isSystemApp); - when(mMockPackageManager.getInstallSourceInfo(componentName.getPackageName())).thenReturn( - installSourceInfo); - } - - private InstallSourceInfo initInstallSourceInfo(String packageName, boolean isSystemApp) - throws PackageManager.NameNotFoundException { - final InstallSourceInfo installSourceInfo = new InstallSourceInfo( - packageName, new SigningInfo(), null, - packageName, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED); - when(mMockPackageManager.getPackageInfo(packageName, 0)).thenReturn( - mMockSourcePackageInfo); - mMockSourcePackageInfo.applicationInfo = mMockSourceApplicationInfo; - when(mMockSourceApplicationInfo.isSystemApp()).thenReturn(isSystemApp); - return installSourceInfo; } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java index b8535c22451f..3cd967db3d91 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java @@ -116,8 +116,7 @@ public class PolicyWarningUIControllerTest { mMockResolveInfo.serviceInfo = mMockServiceInfo; when(mMockA11yServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME); - when(mAccessibilitySecurityPolicy.isA11yCategoryService( - mMockA11yServiceInfo)).thenReturn(false); + when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(false); mFakeNotificationController.onReceive(mContext, PolicyWarningUIController.createIntent(mContext, TEST_USER_ID, diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java index c0ba3c72beb4..5b6aebccbc11 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -45,6 +45,7 @@ public class AudioServiceTest { private Context mContext; private AudioSystemAdapter mAudioSystem; @Spy private SystemServerAdapter mSpySystemServer; + private SettingsAdapter mSettingsAdapter; // the class being unit-tested here private AudioService mAudioService; @@ -59,7 +60,9 @@ public class AudioServiceTest { mContext = InstrumentationRegistry.getTargetContext(); mAudioSystem = new NoOpAudioSystemAdapter(); mSpySystemServer = spy(new NoOpSystemServerAdapter()); - mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer); + mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer, + mSettingsAdapter); } /** diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpSettingsAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpSettingsAdapter.java new file mode 100644 index 000000000000..bf80f27b432c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpSettingsAdapter.java @@ -0,0 +1,102 @@ +/* + * 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.audio; + +import android.content.ContentResolver; + +import java.util.HashMap; +import java.util.Map; + +public class NoOpSettingsAdapter extends SettingsAdapter { + + /** + * No-op methods for Settings.Global + */ + + private Map<String, Integer> mGlobalIntSettings = new HashMap<>(); + private Map<String, String> mGlobalStringSettings = new HashMap<>(); + + @Override + public int getGlobalInt(ContentResolver cr, String name, int def) { + return mGlobalIntSettings.getOrDefault(name, def); + } + + @Override + public String getGlobalString(ContentResolver resolver, String name) { + return mGlobalStringSettings.getOrDefault(name, null); + } + + @Override + public boolean putGlobalInt(ContentResolver cr, String name, int value) { + mGlobalIntSettings.put(name, value); + return true; + } + + @Override + public boolean putGlobalString(ContentResolver resolver, String name, String value) { + mGlobalStringSettings.put(name, value); + return true; + } + + /** + * No-op methods for Settings.System + */ + + private Map<String, Integer> mSystemIntSettings = new HashMap<>(); + + @Override + public int getSystemIntForUser(ContentResolver cr, String name, int def, int userHandle) { + return mSystemIntSettings.getOrDefault(name, def); + } + + @Override + public boolean putSystemIntForUser(ContentResolver cr, String name, int value, int userHandle) { + mSystemIntSettings.put(name, value); + return true; + } + + /** + * No-op methods for Settings.Secure + */ + + private Map<String, Integer> mSecureIntSettings = new HashMap<>(); + private Map<String, String> mSecureStringSettings = new HashMap<>(); + + @Override + public int getSecureIntForUser(ContentResolver cr, String name, int def, int userHandle) { + return mSecureIntSettings.getOrDefault(name, def); + } + + @Override + public String getSecureStringForUser(ContentResolver resolver, String name, int userHandle) { + return mSecureStringSettings.getOrDefault(name, null); + } + + @Override + public boolean putSecureIntForUser(ContentResolver cr, String name, int value, int userHandle) { + mSecureIntSettings.put(name, value); + return true; + } + + @Override + public boolean putSecureStringForUser(ContentResolver cr, String name, String value, + int userHandle) { + mSecureStringSettings.put(name, value); + return true; + } +} + diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 61d7ede98f45..ef9f90fcd667 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -530,5 +530,10 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi return true; } + + @Override + public Context createContextAsUser(UserHandle user) { + return context; + } } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 89bd10fdcdc3..484dc8450c57 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -158,7 +158,6 @@ import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.mockito.internal.util.collections.Sets; @@ -187,7 +186,6 @@ import java.util.concurrent.TimeUnit; */ @SmallTest @Presubmit -@Ignore("b/225415867") public class DevicePolicyManagerTest extends DpmTestBase { private static final String TAG = DevicePolicyManagerTest.class.getSimpleName(); @@ -1786,10 +1784,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { final int userId = CALLER_USER_HANDLE; final UserHandle user = UserHandle.of(userId); - mContext.applicationInfo = new ApplicationInfo(); - mContext.callerPermissions.add(permission.MANAGE_USERS); - mContext.packageName = "com.android.frameworks.servicestests"; - getServices().addPackageContext(user, mContext); + mServiceContext.packageName = mRealTestContext.getPackageName(); + mServiceContext.applicationInfo = new ApplicationInfo(); + mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID; when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE); StringParceledListSlice oneCert = asSlice(new String[] {"1"}); @@ -6281,6 +6278,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception { mServiceContext.packageName = mRealTestContext.getPackageName(); + mServiceContext.applicationInfo = new ApplicationInfo(); mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setDeviceOwner(); @@ -6291,6 +6289,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception { mServiceContext.packageName = mRealTestContext.getPackageName(); + mServiceContext.applicationInfo = new ApplicationInfo(); mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID; setAsProfileOwner(admin1); @@ -6302,6 +6301,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testGetOwnerInstalledCaCertsForDelegate() throws Exception { mServiceContext.packageName = mRealTestContext.getPackageName(); + mServiceContext.applicationInfo = new ApplicationInfo(); mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID; mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID; setAsProfileOwner(admin1); diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java new file mode 100644 index 000000000000..150e3c60b91e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.job; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.app.job.JobInfo; +import android.content.ComponentName; +import android.platform.test.annotations.LargeTest; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.util.SparseLongArray; + +import com.android.server.job.controllers.JobStatus; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class PendingJobQueueTest { + private static final String TAG = PendingJobQueueTest.class.getSimpleName(); + + private static final int[] sRegJobPriorities = { + JobInfo.PRIORITY_HIGH, JobInfo.PRIORITY_DEFAULT, + JobInfo.PRIORITY_LOW, JobInfo.PRIORITY_MIN + }; + + private static JobInfo.Builder createJobInfo(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName("foo", "bar")); + } + + private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder, + int callingUid) { + return JobStatus.createFromJobInfo( + jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag); + } + + @Test + public void testAdd() { + List<JobStatus> jobs = new ArrayList<>(); + jobs.add(createJobStatus("testAdd", createJobInfo(1), 1)); + jobs.add(createJobStatus("testAdd", createJobInfo(2), 2)); + jobs.add(createJobStatus("testAdd", createJobInfo(3).setExpedited(true), 3)); + jobs.add(createJobStatus("testAdd", createJobInfo(4), 4)); + jobs.add(createJobStatus("testAdd", createJobInfo(5).setExpedited(true), 5)); + + PendingJobQueue jobQueue = new PendingJobQueue(); + for (int i = 0; i < jobs.size(); ++i) { + jobQueue.add(jobs.get(i)); + assertEquals(i + 1, jobQueue.size()); + } + + JobStatus job; + while ((job = jobQueue.next()) != null) { + jobs.remove(job); + } + assertEquals(0, jobs.size()); + } + + @Test + public void testAddAll() { + List<JobStatus> jobs = new ArrayList<>(); + jobs.add(createJobStatus("testAddAll", createJobInfo(1), 1)); + jobs.add(createJobStatus("testAddAll", createJobInfo(2), 2)); + jobs.add(createJobStatus("testAddAll", createJobInfo(3).setExpedited(true), 3)); + jobs.add(createJobStatus("testAddAll", createJobInfo(4), 4)); + jobs.add(createJobStatus("testAddAll", createJobInfo(5).setExpedited(true), 5)); + + PendingJobQueue jobQueue = new PendingJobQueue(); + jobQueue.addAll(jobs); + assertEquals(jobs.size(), jobQueue.size()); + + JobStatus job; + while ((job = jobQueue.next()) != null) { + jobs.remove(job); + } + assertEquals(0, jobs.size()); + } + + @Test + public void testClear() { + List<JobStatus> jobs = new ArrayList<>(); + jobs.add(createJobStatus("testClear", createJobInfo(1), 1)); + jobs.add(createJobStatus("testClear", createJobInfo(2), 2)); + jobs.add(createJobStatus("testClear", createJobInfo(3).setExpedited(true), 3)); + jobs.add(createJobStatus("testClear", createJobInfo(4), 4)); + jobs.add(createJobStatus("testClear", createJobInfo(5).setExpedited(true), 5)); + + PendingJobQueue jobQueue = new PendingJobQueue(); + jobQueue.addAll(jobs); + assertEquals(jobs.size(), jobQueue.size()); + assertNotNull(jobQueue.next()); + + jobQueue.clear(); + assertEquals(0, jobQueue.size()); + assertNull(jobQueue.next()); + } + + @Test + public void testRemove() { + List<JobStatus> jobs = new ArrayList<>(); + jobs.add(createJobStatus("testRemove", createJobInfo(1), 1)); + jobs.add(createJobStatus("testRemove", createJobInfo(2), 2)); + jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3)); + jobs.add(createJobStatus("testRemove", createJobInfo(4), 4)); + jobs.add(createJobStatus("testRemove", createJobInfo(5).setExpedited(true), 5)); + + PendingJobQueue jobQueue = new PendingJobQueue(); + jobQueue.addAll(jobs); + + for (int i = 0; i < jobs.size(); ++i) { + jobQueue.remove(jobs.get(i)); + assertEquals(jobs.size() - i - 1, jobQueue.size()); + } + assertNull(jobQueue.next()); + } + + @Test + public void testPendingJobSorting() { + PendingJobQueue jobQueue = new PendingJobQueue(); + + // First letter in job variable name indicate regular (r) or expedited (e). + // Capital letters in job variable name indicate the app/UID. + // Numbers in job variable name indicate the enqueue time. + // Expected sort order: + // eA7 > rA1 > eB6 > rB2 > eC3 > rD4 > eE5 > eF9 > rF8 > eC11 > rC10 > rG12 > rG13 > eE14 + // Intentions: + // * A jobs let us test skipping both regular and expedited jobs of other apps + // * B jobs let us test skipping only regular job of another app without going too far + // * C jobs test that regular jobs don't skip over other app's jobs and that EJs only + // skip up to level of the earliest regular job + // * E jobs test that expedited jobs don't skip the line when the app has no regular jobs + // * F jobs test correct expedited/regular ordering doesn't push jobs too high in list + // * G jobs test correct ordering for regular jobs + // * H job tests correct behavior when enqueue times are the same + JobStatus rA1 = createJobStatus("testPendingJobSorting", createJobInfo(1), 1); + JobStatus rB2 = createJobStatus("testPendingJobSorting", createJobInfo(2), 2); + JobStatus eC3 = createJobStatus("testPendingJobSorting", + createJobInfo(3).setExpedited(true), 3); + JobStatus rD4 = createJobStatus("testPendingJobSorting", createJobInfo(4), 4); + JobStatus eE5 = createJobStatus("testPendingJobSorting", + createJobInfo(5).setExpedited(true), 5); + JobStatus eB6 = createJobStatus("testPendingJobSorting", + createJobInfo(6).setExpedited(true), 2); + JobStatus eA7 = createJobStatus("testPendingJobSorting", + createJobInfo(7).setExpedited(true), 1); + JobStatus rH8 = createJobStatus("testPendingJobSorting", createJobInfo(8), 8); + JobStatus rF8 = createJobStatus("testPendingJobSorting", createJobInfo(8), 6); + JobStatus eF9 = createJobStatus("testPendingJobSorting", + createJobInfo(9).setExpedited(true), 6); + JobStatus rC10 = createJobStatus("testPendingJobSorting", createJobInfo(10), 3); + JobStatus eC11 = createJobStatus("testPendingJobSorting", + createJobInfo(11).setExpedited(true), 3); + JobStatus rG12 = createJobStatus("testPendingJobSorting", createJobInfo(12), 7); + JobStatus rG13 = createJobStatus("testPendingJobSorting", createJobInfo(13), 7); + JobStatus eE14 = createJobStatus("testPendingJobSorting", + createJobInfo(14).setExpedited(true), 5); + + rA1.enqueueTime = 10; + rB2.enqueueTime = 20; + eC3.enqueueTime = 30; + rD4.enqueueTime = 40; + eE5.enqueueTime = 50; + eB6.enqueueTime = 60; + eA7.enqueueTime = 70; + rF8.enqueueTime = 80; + rH8.enqueueTime = 80; + eF9.enqueueTime = 90; + rC10.enqueueTime = 100; + eC11.enqueueTime = 110; + rG12.enqueueTime = 120; + rG13.enqueueTime = 130; + eE14.enqueueTime = 140; + + // Add in random order so sorting is apparent. + jobQueue.add(eC3); + jobQueue.add(eE5); + jobQueue.add(rA1); + jobQueue.add(rG13); + jobQueue.add(rD4); + jobQueue.add(eA7); + jobQueue.add(rG12); + jobQueue.add(rH8); + jobQueue.add(rF8); + jobQueue.add(eB6); + jobQueue.add(eE14); + jobQueue.add(eF9); + jobQueue.add(rB2); + jobQueue.add(rC10); + jobQueue.add(eC11); + + checkPendingJobInvariants(jobQueue); + final JobStatus[] expectedOrder = new JobStatus[]{ + eA7, rA1, eB6, rB2, eC3, rD4, eE5, eF9, rH8, rF8, eC11, rC10, rG12, rG13, eE14}; + int idx = 0; + JobStatus job; + while ((job = jobQueue.next()) != null) { + assertEquals("List wasn't correctly sorted @ index " + idx, + expectedOrder[idx].getJobId(), job.getJobId()); + idx++; + } + } + + @Test + public void testPendingJobSorting_Random() { + PendingJobQueue jobQueue = new PendingJobQueue(); + Random random = new Random(1); // Always use the same series of pseudo random values. + + for (int i = 0; i < 5000; ++i) { + JobStatus job = createJobStatus("testPendingJobSorting_Random", + createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(250)); + job.enqueueTime = random.nextInt(1_000_000); + jobQueue.add(job); + } + + checkPendingJobInvariants(jobQueue); + } + + @Test + public void testPendingJobSortingTransitivity() { + PendingJobQueue jobQueue = new PendingJobQueue(); + // Always use the same series of pseudo random values. + for (int seed : new int[]{1337, 7357, 606, 6357, 41106010, 3, 2, 1}) { + Random random = new Random(seed); + + jobQueue.clear(); + + for (int i = 0; i < 300; ++i) { + JobStatus job = createJobStatus("testPendingJobSortingTransitivity", + createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(50)); + job.enqueueTime = random.nextInt(1_000_000); + job.overrideState = random.nextInt(4); + jobQueue.add(job); + } + + checkPendingJobInvariants(jobQueue); + } + } + + @Test + @LargeTest + public void testPendingJobSortingTransitivity_Concentrated() { + PendingJobQueue jobQueue = new PendingJobQueue(); + // Always use the same series of pseudo random values. + for (int seed : new int[]{1337, 6000, 637739, 6357, 1, 7, 13}) { + Random random = new Random(seed); + + jobQueue.clear(); + + for (int i = 0; i < 300; ++i) { + JobStatus job = createJobStatus("testPendingJobSortingTransitivity_Concentrated", + createJobInfo(i).setExpedited(random.nextFloat() < .03), + random.nextInt(20)); + job.enqueueTime = random.nextInt(250); + job.overrideState = random.nextFloat() < .01 + ? JobStatus.OVERRIDE_SORTING : JobStatus.OVERRIDE_NONE; + jobQueue.add(job); + Log.d(TAG, testJobToString(job)); + } + + checkPendingJobInvariants(jobQueue); + } + } + + @Test + public void testPendingJobSorting_Random_WithPriority() { + PendingJobQueue jobQueue = new PendingJobQueue(); + Random random = new Random(1); // Always use the same series of pseudo random values. + + for (int i = 0; i < 5000; ++i) { + final boolean isEj = random.nextBoolean(); + final int priority; + if (isEj) { + priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH; + } else { + priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)]; + } + JobStatus job = createJobStatus("testPendingJobSorting_Random_WithPriority", + createJobInfo(i).setExpedited(isEj).setPriority(priority), + random.nextInt(250)); + job.enqueueTime = random.nextInt(1_000_000); + jobQueue.add(job); + } + + checkPendingJobInvariants(jobQueue); + } + + @Test + public void testPendingJobSortingTransitivity_WithPriority() { + PendingJobQueue jobQueue = new PendingJobQueue(); + // Always use the same series of pseudo random values. + for (int seed : new int[]{1337, 7357, 606, 6357, 41106010, 3, 2, 1}) { + Random random = new Random(seed); + + jobQueue.clear(); + + for (int i = 0; i < 300; ++i) { + final boolean isEj = random.nextBoolean(); + final int priority; + if (isEj) { + priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH; + } else { + priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)]; + } + JobStatus job = createJobStatus("testPendingJobSortingTransitivity_WithPriority", + createJobInfo(i).setExpedited(isEj).setPriority(priority), + random.nextInt(50)); + job.enqueueTime = random.nextInt(1_000_000); + job.overrideState = random.nextInt(4); + jobQueue.add(job); + } + + checkPendingJobInvariants(jobQueue); + } + } + + @Test + @LargeTest + public void testPendingJobSortingTransitivity_Concentrated_WithPriority() { + PendingJobQueue jobQueue = new PendingJobQueue(); + // Always use the same series of pseudo random values. + for (int seed : new int[]{1337, 6000, 637739, 6357, 1, 7, 13}) { + Random random = new Random(seed); + + jobQueue.clear(); + + for (int i = 0; i < 300; ++i) { + final boolean isEj = random.nextFloat() < .03; + final int priority; + if (isEj) { + priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH; + } else { + priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)]; + } + JobStatus job = createJobStatus( + "testPendingJobSortingTransitivity_Concentrated_WithPriority", + createJobInfo(i).setExpedited(isEj).setPriority(priority), + random.nextInt(20)); + job.enqueueTime = random.nextInt(250); + job.overrideState = random.nextFloat() < .01 + ? JobStatus.OVERRIDE_SORTING : JobStatus.OVERRIDE_NONE; + jobQueue.add(job); + Log.d(TAG, testJobToString(job)); + } + + checkPendingJobInvariants(jobQueue); + } + } + + private void checkPendingJobInvariants(PendingJobQueue jobQueue) { + final SparseBooleanArray regJobSeen = new SparseBooleanArray(); + final SparseIntArray lastOverrideStateSeen = new SparseIntArray(); + // Latest priority enqueue times seen for each priority for each app. + final SparseArray<SparseLongArray> latestPriorityRegEnqueueTimesPerUid = + new SparseArray<>(); + final SparseArray<SparseLongArray> latestPriorityEjEnqueueTimesPerUid = new SparseArray<>(); + final int noEntry = -1; + + JobStatus job; + jobQueue.resetIterator(); + while ((job = jobQueue.next()) != null) { + final int uid = job.getSourceUid(); + + // Invariant #1: All jobs (for a UID) are sorted by override state + // Invariant #2: All jobs (for a UID) are sorted by priority order + // Invariant #3: Jobs (for a UID) with the same priority are sorted by enqueue time. + // Invariant #4: EJs (for a UID) should be before regular jobs + + final int prevOverrideState = lastOverrideStateSeen.get(uid, noEntry); + lastOverrideStateSeen.put(uid, job.overrideState); + if (prevOverrideState == noEntry) { + // First job for UID + continue; + } + + // Invariant 1 + if (prevOverrideState != job.overrideState) { + assertTrue(prevOverrideState > job.overrideState); + // Override state can make ordering weird. Clear the other cached states for this + // UID to avoid confusion in the other checks. + latestPriorityEjEnqueueTimesPerUid.remove(uid); + latestPriorityRegEnqueueTimesPerUid.remove(uid); + regJobSeen.delete(uid); + } + + final int priority = job.getEffectivePriority(); + final SparseArray<SparseLongArray> latestPriorityEnqueueTimesPerUid = + job.isRequestedExpeditedJob() + ? latestPriorityEjEnqueueTimesPerUid + : latestPriorityRegEnqueueTimesPerUid; + SparseLongArray latestPriorityEnqueueTimes = latestPriorityEnqueueTimesPerUid.get(uid); + if (latestPriorityEnqueueTimes != null) { + // Invariant 2 + for (int p = priority - 1; p >= JobInfo.PRIORITY_MIN; --p) { + // If we haven't seen the priority, there shouldn't be an entry in the array. + assertEquals("Jobs not properly sorted by priority for uid " + uid, + noEntry, latestPriorityEnqueueTimes.get(p, noEntry)); + } + + // Invariant 3 + final long lastSeenPriorityEnqueueTime = + latestPriorityEnqueueTimes.get(priority, noEntry); + if (lastSeenPriorityEnqueueTime != noEntry) { + assertTrue("Jobs with same priority not sorted by enqueue time: " + + lastSeenPriorityEnqueueTime + " vs " + job.enqueueTime, + lastSeenPriorityEnqueueTime <= job.enqueueTime); + } + } else { + latestPriorityEnqueueTimes = new SparseLongArray(); + latestPriorityEnqueueTimesPerUid.put(uid, latestPriorityEnqueueTimes); + } + latestPriorityEnqueueTimes.put(priority, job.enqueueTime); + + // Invariant 4 + if (!job.isRequestedExpeditedJob()) { + regJobSeen.put(uid, true); + } else if (regJobSeen.get(uid)) { + fail("UID " + uid + " had an EJ ordered after a regular job"); + } + } + } + + private static String testJobToString(JobStatus job) { + return "testJob " + job.getSourceUid() + "/" + job.getJobId() + + "/o" + job.overrideState + + "/p" + job.getEffectivePriority() + + "/b" + job.lastEvaluatedBias + + "/" + job.isRequestedExpeditedJob() + "@" + job.enqueueTime; + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index af8ac6f412f5..7634b09b4dc6 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -118,6 +118,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; import android.content.pm.UserInfo; import android.net.ConnectivityManager; @@ -166,6 +167,7 @@ import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent; import com.android.internal.util.test.FsUtil; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; +import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.usage.AppStandbyInternal; import com.google.common.util.concurrent.AbstractFuture; @@ -216,6 +218,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; import java.util.stream.Collectors; /** @@ -274,6 +277,7 @@ public class NetworkPolicyManagerServiceTest { ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); private ActivityManagerInternal mActivityManagerInternal; + private PackageManagerInternal mPackageManagerInternal; private IUidObserver mUidObserver; private INetworkManagementEventObserver mNetworkObserver; @@ -335,6 +339,7 @@ public class NetworkPolicyManagerServiceTest { when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{}); mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class); + mPackageManagerInternal = addLocalServiceMock(PackageManagerInternal.class); final PowerSaveState state = new PowerSaveState.Builder() .setBatterySaverEnabled(false).build(); @@ -483,8 +488,15 @@ public class NetworkPolicyManagerServiceTest { .thenReturn(buildApplicationInfo(PKG_NAME_B, UID_B)); when(mPackageManager.getApplicationInfo(eq(PKG_NAME_C), anyInt())) .thenReturn(buildApplicationInfo(PKG_NAME_C, UID_C)); - when(mPackageManager.getInstalledApplications(anyInt())).thenReturn( - buildInstalledApplicationInfoList()); + doAnswer(arg -> { + final Consumer<AndroidPackage> consumer = + (Consumer<AndroidPackage>) arg.getArguments()[0]; + for (AndroidPackage androidPackage : buildInstalledPackageList()) { + consumer.accept(androidPackage); + } + return null; + }).when(mPackageManagerInternal).forEachInstalledPackage( + any(Consumer.class), anyInt()); when(mUserManager.getUsers()).thenReturn(buildUserInfoList()); when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true); when(mNetworkManager.setDataSaverModeEnabled(anyBoolean())).thenReturn(true); @@ -536,6 +548,7 @@ public class NetworkPolicyManagerServiceTest { LocalServices.removeServiceForTest(DeviceIdleInternal.class); LocalServices.removeServiceForTest(AppStandbyInternal.class); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); } @After @@ -2037,14 +2050,20 @@ public class NetworkPolicyManagerServiceTest { return ai; } - private List<ApplicationInfo> buildInstalledApplicationInfoList() { - final List<ApplicationInfo> installedApps = new ArrayList<>(); - installedApps.add(buildApplicationInfo(PKG_NAME_A, UID_A)); - installedApps.add(buildApplicationInfo(PKG_NAME_B, UID_B)); - installedApps.add(buildApplicationInfo(PKG_NAME_C, UID_C)); + private List<AndroidPackage> buildInstalledPackageList() { + final List<AndroidPackage> installedApps = new ArrayList<>(); + installedApps.add(createPackageMock(UID_A)); + installedApps.add(createPackageMock(UID_B)); + installedApps.add(createPackageMock(UID_C)); return installedApps; } + private AndroidPackage createPackageMock(int uid) { + final AndroidPackage androidPackage = mock(AndroidPackage.class); + when(androidPackage.getUid()).thenReturn(uid); + return androidPackage; + } + private List<UserInfo> buildUserInfoList() { final List<UserInfo> users = new ArrayList<>(); users.add(new UserInfo(USER_ID, "user1", 0)); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index 0301e94a6acc..b907c62be6fb 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -277,6 +277,6 @@ public class VibrationScalerTest { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); // FakeSettingsProvider don't support testing triggering ContentObserver yet. - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index ec16188bfc1d..0c28d8c761ab 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -476,6 +476,24 @@ public class VibrationSettingsTest { } @Test + public void shouldIgnoreVibration_updateTriggeredAfterInternalRingerModeChanged() { + // Vibrating settings on are overruled by ringer mode. + setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1); + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + + assertVibrationNotIgnoredForUsage(USAGE_RINGTONE); + + // Testing the broadcast flow manually. + mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT); + mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); + + assertVibrationIgnoredForUsage(USAGE_RINGTONE, Vibration.Status.IGNORED_FOR_RINGER_MODE); + } + + @Test public void shouldVibrateInputDevices_returnsSettingsValue() { setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); assertTrue(mVibrationSettings.shouldVibrateInputDevices()); @@ -577,7 +595,7 @@ public class VibrationSettingsTest { Settings.System.putIntForUser(mContextSpy.getContentResolver(), Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW, UserHandle.USER_CURRENT); - mVibrationSettings.mUserReceiver.onReceive(mContextSpy, + mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED)); assertEquals(VIBRATION_INTENSITY_LOW, mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); @@ -587,7 +605,7 @@ public class VibrationSettingsTest { public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() { setDefaultIntensity(USAGE_HARDWARE_FEEDBACK, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); // If haptic feedback is off, fallback to default value. assertEquals(VIBRATION_INTENSITY_MEDIUM, @@ -596,7 +614,7 @@ public class VibrationSettingsTest { mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH); - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); // If haptic feedback is on, fallback to that value. @@ -633,7 +651,7 @@ public class VibrationSettingsTest { mVibrationSettings.shouldIgnoreVibration(UID, new VibrationAttributes.Builder() .setUsage(usage) - .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) + .setFlags(flags) .build())); } @@ -654,18 +672,19 @@ public class VibrationSettingsTest { Settings.System.putStringForUser( mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT); // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); } private void setUserSetting(String settingName, int value) { Settings.System.putIntForUser( mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); // FakeSettingsProvider doesn't support testing triggering ContentObserver yet. - mVibrationSettings.updateSettings(); + mVibrationSettings.update(); } private void setRingerMode(int ringerMode) { mAudioManager.setRingerModeInternal(ringerMode); assertEquals(ringerMode, mAudioManager.getRingerModeInternal()); + mVibrationSettings.update(); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 704729ea22c4..9f135918daa2 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -257,13 +257,13 @@ public class VibrationThreadTest { assertTrue(mThread.isRunningVibrationId(vibrationId)); assertTrue(mControllers.get(VIBRATOR_ID).isVibrating()); - conductor.notifyCancelled(/* immediate= */ false); + conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false); waitForCompletion(); assertFalse(mThread.isRunningVibrationId(vibrationId)); verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong()); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); List<Float> playedAmplitudes = fakeVibrator.getAmplitudes(); @@ -288,10 +288,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); - conductor.notifyCancelled(/* immediate= */ false); + conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(1000)), fakeVibrator.getEffectSegments(vibrationId)); @@ -310,10 +310,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); - conductor.notifyCancelled(/* immediate= */ false); + conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(5550)), fakeVibrator.getEffectSegments(vibrationId)); @@ -334,10 +334,10 @@ public class VibrationThreadTest { assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, 1000 + TEST_TIMEOUT_MILLIS)); - conductor.notifyCancelled(/* immediate= */ false); + conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size()); // First time turn vibrator ON for minimum of 1s. @@ -371,13 +371,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = - new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); + new Thread(() -> conductor.notifyCancelled( + Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -397,13 +398,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = - new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); + new Thread(() -> conductor.notifyCancelled( + Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -647,7 +649,7 @@ public class VibrationThreadTest { waitForCompletion(); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED); } @Test @@ -1043,7 +1045,8 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(cancellingThread). Thread cancellingThread = new Thread( - () -> conductor.notifyCancelled(/* immediate= */ false)); + () -> conductor.notifyCancelled( + Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false)); cancellingThread.start(); // Cancelling the vibration should be fast and return right away, even if the thread is @@ -1052,7 +1055,7 @@ public class VibrationThreadTest { // After the vibrator call ends the vibration is cancelled and the vibrator is turned off. waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -1080,13 +1083,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = new Thread( - () -> conductor.notifyCancelled(/* immediate= */ false)); + () -> conductor.notifyCancelled( + Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } @@ -1113,13 +1117,14 @@ public class VibrationThreadTest { // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. Thread cancellingThread = - new Thread(() -> conductor.notifyCancelled(/* immediate= */ false)); + new Thread(() -> conductor.notifyCancelled( + Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false)); cancellingThread.start(); waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } @@ -1139,7 +1144,7 @@ public class VibrationThreadTest { verify(mVibrationToken).linkToDeath(same(conductor), eq(0)); verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0)); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED); assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty()); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -1193,12 +1198,13 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); // Will stop the ramp down right away. - conductor.notifyCancelled(/* immediate= */ true); + conductor.notifyCancelled( + Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ true); waitForCompletion(); // Does not cancel already finished vibration, but releases vibrator. verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId), - eq(Vibration.Status.CANCELLED)); + eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)); verify(mManagerHooks).onVibrationThreadReleased(vibrationId); } @@ -1214,10 +1220,10 @@ public class VibrationThreadTest { VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); - conductor.notifyCancelled(/* immediate= */ false); + conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); // Duration extended for 10000 + 15. assertEquals(Arrays.asList(expectedOneShot(10_015)), @@ -1337,7 +1343,7 @@ public class VibrationThreadTest { VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2); // Effect2 won't complete on its own. Cancel it after a couple of repeats. Thread.sleep(150); // More than two TICKs. - conductor2.notifyCancelled(/* immediate= */ false); + conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); startThreadAndDispatcher(vibrationId3, effect3); @@ -1346,7 +1352,7 @@ public class VibrationThreadTest { // Effect4 is a long oneshot, but it gets cancelled as fast as possible. long start4 = System.currentTimeMillis(); VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4); - conductor4.notifyCancelled(/* immediate= */ true); + conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true); waitForCompletion(); long duration4 = System.currentTimeMillis() - start4; @@ -1366,7 +1372,7 @@ public class VibrationThreadTest { // Effect2: repeating, cancelled. verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2); - verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER); // The exact count of segments might vary, so just check that there's more than 2 and // all elements are the same segment. @@ -1384,7 +1390,7 @@ public class VibrationThreadTest { fakeVibrator.getEffectSegments(vibrationId3)); // Effect4: cancelled quickly. - verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED); + verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED); assertTrue("Tested duration=" + duration4, duration4 < 2000); // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 92736c517782..9c72ce22c857 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -673,6 +673,42 @@ public class VibratorManagerServiceTest { } @Test + public void vibrate_withVibrationAttributesEnforceFreshSettings_refreshesVibrationSettings() + throws Exception { + mockVibrators(0); + mVibratorProviders.get(0).setSupportedEffects(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_TICK); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_HIGH); + VibratorManagerService service = createSystemReadyService(); + + VibrationAttributes enforceFreshAttrs = new VibrationAttributes.Builder() + .setUsage(VibrationAttributes.USAGE_NOTIFICATION) + .setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE) + .build(); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), NOTIFICATION_ATTRS); + // VibrationThread will start this vibration async, so wait before vibrating a second time. + assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 0, + service, TEST_TIMEOUT_MILLIS)); + + vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), enforceFreshAttrs); + // VibrationThread will start this vibration async, so wait before checking. + assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 1, + service, TEST_TIMEOUT_MILLIS)); + + assertEquals( + Arrays.asList( + expectedPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_STRONG), + expectedPrebaked(VibrationEffect.EFFECT_TICK, + VibrationEffect.EFFECT_STRENGTH_LIGHT)), + mVibratorProviders.get(0).getAllEffectSegments()); + } + + @Test public void vibrate_withAttributesUnknownUsage_usesEffectToIdentifyTouchUsage() { VibratorManagerService service = createSystemReadyService(); @@ -1280,7 +1316,11 @@ public class VibratorManagerServiceTest { } private VibrationEffectSegment expectedPrebaked(int effectId) { - return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + } + + private VibrationEffectSegment expectedPrebaked(int effectId, int effectStrength) { + return new PrebakedSegment(effectId, false, effectStrength); } private void mockCapabilities(long... capabilities) { 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 40b460157bc2..91692676e216 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -414,6 +414,33 @@ public class DisplayContentTests extends WindowTestsBase { imeContainer.setOrganizer(null); } + @Test + public void testImeContainerIsReparentedUnderParentWhenOrganized() { + final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + + final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, + "startingWin"); + startingWin.setHasSurface(true); + assertTrue(startingWin.canBeImeTarget()); + + final Transaction transaction = mDisplayContent.getPendingTransaction(); + spyOn(transaction); + + // Organized the ime container. + final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class); + when(mockImeOrganizer.asBinder()).thenReturn(new Binder()); + imeContainer.setOrganizer(mockImeOrganizer); + + // Verify that the ime container surface is reparented under + // its parent surface as a consequence of the setOrganizer call. + SurfaceControl imeParentSurfaceControl = imeContainer.getParentSurfaceControl(); + verify(transaction).reparent(imeContainer.getSurfaceControl(), imeParentSurfaceControl); + + // Clean up organizer. + imeContainer.setOrganizer(null); + } + /** * This tests root task movement between displays and proper root task's, task's and app token's * display container references updates. @@ -1984,7 +2011,7 @@ public class DisplayContentTests extends WindowTestsBase { appWin2.setHasSurface(true); assertTrue(appWin2.canBeImeTarget()); doReturn(true).when(appWin1).isClosing(); - doReturn(true).when(appWin1).inAppOrRecentsTransition(); + doReturn(true).when(appWin1).inTransitionSelfOrParent(); // Test step 3: Verify appWin2 will be the next IME target and the IME snapshot surface will // be attached and shown on the display at this time. diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index b2f0b420d67c..ce861595535c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; import static android.view.Display.DEFAULT_DISPLAY; @@ -190,7 +191,11 @@ public class SystemServicesTestRule implements TestRule { private void setUpSystemCore() { doReturn(mock(Watchdog.class)).when(Watchdog::getInstance); doAnswer(invocation -> { - mDeviceConfigListeners.add(invocation.getArgument(2)); + // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig + // only registers once and it doesn't reference to outside. + if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) { + mDeviceConfigListeners.add(invocation.getArgument(2)); + } // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests // uses splash_screen_exception_list. So still execute real registration. return invocation.callRealMethod(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index c6158662d110..b1c9d3de304b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; @@ -249,19 +250,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100)); - assertThrows(SecurityException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - } catch (RemoteException e) { - fail(); - } - }); + assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); } @Test @@ -272,19 +267,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.reorder(mFragmentWindowToken, true /* onTop */); - assertThrows(SecurityException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - } catch (RemoteException e) { - fail(); - } - }); + assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); } @Test @@ -298,27 +287,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.deleteTaskFragment(mFragmentWindowToken); - assertThrows(SecurityException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - } catch (RemoteException e) { - fail(); - } - }); + assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); // No lifecycle update when the TaskFragment is not recorded. verify(mAtm.mRootWindowContainer, never()).resumeFocusedTasksTopActivities(); mAtm.mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } @@ -335,13 +318,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */); - assertThrows(SecurityException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - } catch (RemoteException e) { - fail(); - } - }); + assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, @@ -350,7 +327,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } @@ -423,20 +400,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.reparentChildren(mFragmentWindowToken, null /* newParent */); - assertThrows(SecurityException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); - } catch (RemoteException e) { - fail(); - } - }); + assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } @@ -454,6 +425,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mAtm.mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token); + doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); @@ -484,6 +456,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); } + @Test + public void testCanSendPendingTaskFragmentEventsAfterActivityResumed() { + // Task - TaskFragment - Activity. + final Task task = createTask(mDisplayContent); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(mFragmentToken) + .createActivityCount(1) + .build(); + final ActivityRecord activity = taskFragment.getTopMostActivity(); + + // Mock the task to invisible + doReturn(false).when(task).shouldBeVisible(any()); + taskFragment.setResumedActivity(null, "test"); + + // Sending events + mController.registerOrganizer(mIOrganizer); + taskFragment.mTaskFragmentAppearedSent = true; + mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); + mController.dispatchPendingEvents(); + + // Verifies that event was not sent + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + + // Mock the task becomes visible, and activity resumed + doReturn(true).when(task).shouldBeVisible(any()); + taskFragment.setResumedActivity(activity, "test"); + + // Verifies that event is sent. + mController.dispatchPendingEvents(); + verify(mOrganizer).onTaskFragmentInfoChanged(any()); + } + /** * Tests that a task fragment info changed event is still sent if the task is invisible only * when the info changed event is because of the last activity in a task finishing. @@ -541,6 +547,66 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } /** + * For config change to untrusted embedded TaskFragment, we only allow bounds change within + * its parent bounds. + */ + @Test + public void testUntrustedEmbedding_configChange() throws RemoteException { + mController.registerOrganizer(mIOrganizer); + mOrganizer.applyTransaction(mTransaction); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode(); + final Task task = createTask(mDisplayContent); + final Rect taskBounds = new Rect(task.getBounds()); + final Rect taskAppBounds = new Rect(task.getWindowConfiguration().getAppBounds()); + final int taskScreenWidthDp = task.getConfiguration().screenWidthDp; + final int taskScreenHeightDp = task.getConfiguration().screenHeightDp; + final int taskSmallestScreenWidthDp = task.getConfiguration().smallestScreenWidthDp; + task.addChild(mTaskFragment, POSITION_TOP); + + // Throw exception if the transaction is trying to change bounds of an untrusted outside of + // its parent's. + + // setBounds + final Rect tfBounds = new Rect(taskBounds); + tfBounds.right++; + mTransaction.setBounds(mFragmentWindowToken, tfBounds); + assertApplyTransactionDisallowed(mTransaction); + + mTransaction.setBounds(mFragmentWindowToken, taskBounds); + assertApplyTransactionAllowed(mTransaction); + + // setAppBounds + final Rect tfAppBounds = new Rect(taskAppBounds); + tfAppBounds.right++; + mTransaction.setAppBounds(mFragmentWindowToken, tfAppBounds); + assertApplyTransactionDisallowed(mTransaction); + + mTransaction.setAppBounds(mFragmentWindowToken, taskAppBounds); + assertApplyTransactionAllowed(mTransaction); + + // setScreenSizeDp + mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp + 1, + taskScreenHeightDp + 1); + assertApplyTransactionDisallowed(mTransaction); + + mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp, taskScreenHeightDp); + assertApplyTransactionAllowed(mTransaction); + + // setSmallestScreenWidthDp + mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp + 1); + assertApplyTransactionDisallowed(mTransaction); + + mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp); + assertApplyTransactionAllowed(mTransaction); + + // Any of the change mask is not allowed. + mTransaction.setFocusable(mFragmentWindowToken, false); + assertApplyTransactionDisallowed(mTransaction); + } + + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction} to apply the transaction, */ @@ -556,4 +622,24 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. wct.createTaskFragment(params); } + + /** Asserts that applying the given transaction will throw a {@link SecurityException}. */ + private void assertApplyTransactionDisallowed(WindowContainerTransaction t) { + assertThrows(SecurityException.class, () -> { + try { + mAtm.getWindowOrganizerController().applyTransaction(t); + } catch (RemoteException e) { + fail(); + } + }); + } + + /** Asserts that applying the given transaction will not throw any exception. */ + private void assertApplyTransactionAllowed(WindowContainerTransaction t) { + try { + mAtm.getWindowOrganizerController().applyTransaction(t); + } catch (RemoteException e) { + fail(); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 7e5d017e2bc4..3330ca982068 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -617,8 +617,8 @@ public class TransitionTests extends WindowTestsBase { } player.startTransition(); - assertFalse(statusBar.mToken.inTransition()); - assertFalse(decorToken.inTransition()); + assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken)); + assertFalse(mDisplayContent.mTransitionController.isCollecting(decorToken)); assertTrue(ime.mToken.inTransition()); assertTrue(task.inTransition()); assertTrue(asyncRotationController.isTargetToken(decorToken)); @@ -735,7 +735,7 @@ public class TransitionTests extends WindowTestsBase { statusBar.setOrientationChanging(true); player.startTransition(); // Non-app windows should not be collected. - assertFalse(statusBar.mToken.inTransition()); + assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken)); onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted(); assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange( diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 08320f8c423f..9d4fe2783992 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -70,7 +70,7 @@ import org.junit.runner.RunWith; /** * Build/Install/Run: - * atest WmTests:WindowManagerServiceTests + * atest WmTests:WindowManagerServiceTests */ @SmallTest @Presubmit @@ -266,7 +266,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay); final Session session = new Session(mWm, new IWindowSessionCallback.Stub() { @Override - public void onAnimatorScaleChanged(float v) throws RemoteException {} + public void onAnimatorScaleChanged(float v) throws RemoteException { + } }); final WindowManager.LayoutParams params = new WindowManager.LayoutParams( TYPE_APPLICATION_ATTACHED_DIALOG); @@ -292,7 +293,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString()); - when(mWm.mAtmService.isInstrumenting(callingPid)).thenReturn(true); + when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid, + android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true); mWm.setInTouchMode(!currentTouchMode); @@ -306,7 +308,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString()); - when(mWm.mAtmService.isInstrumenting(callingPid)).thenReturn(false); + when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid, + android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(false); mWm.setInTouchMode(!currentTouchMode); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index b4d305bbe4d0..9faf499f391a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -309,6 +309,51 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testOrganizerDeathReturnsRegistrationToPrevious() throws RemoteException { + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final Task rootTask2 = createRootTask(); + final Task task2 = createTask(rootTask2); + final Task rootTask3 = createRootTask(); + final Task task3 = createTask(rootTask3); + final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>(); + final ITaskOrganizer organizer = registerMockOrganizer(existingTasks); + // Ensure events dispatch to organizer. + mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); + + // verify that tasks are returned and taskAppeared is not called + assertContainsTasks(existingTasks, rootTask, rootTask2, rootTask3); + verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class), + any(SurfaceControl.class)); + verify(organizer, times(0)).onTaskVanished(any()); + assertTrue(rootTask.isOrganized()); + + // Now we replace the registration and verify the new organizer receives existing tasks + final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>(); + final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2); + // Ensure events dispatch to organizer. + mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); + assertContainsTasks(existingTasks2, rootTask, rootTask2, rootTask3); + verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class), + any(SurfaceControl.class)); + verify(organizer2, times(0)).onTaskVanished(any()); + // Removed tasks from the original organizer + assertTaskVanished(organizer, true /* expectVanished */, rootTask, rootTask2, rootTask3); + assertTrue(rootTask2.isOrganized()); + + // Trigger binderDied for second one, the first one should automatically be reregistered + // so we verify that it's now seeing changes. + mWm.mAtmService.mTaskOrganizerController.getTaskOrganizerState(organizer2.asBinder()) + .getDeathRecipient().binderDied(); + + // Ensure events dispatch to organizer. + mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); + verify(organizer, times(3)) + .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); + assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3); + } + + @Test public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException { final Task rootTask = createRootTask(); final Task task = createTask(rootTask); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index bd1f9d536c28..6e0d8549defc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1264,6 +1264,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID, DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); } + spyOn(taskFragment); return taskFragment; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index eea3f844b40f..0f223ca037ee 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -293,8 +293,7 @@ public class ZOrderingTests extends WindowTestsBase { public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() { final WindowState appBelowImeTarget = createWindow("appBelowImeTarget"); final WindowState imeAppTarget = createWindow("imeAppTarget"); - final WindowState appAboveImeTarget = createWindow(imeAppTarget, TYPE_APPLICATION, - "appAboveImeTarget"); + final WindowState appAboveImeTarget = createWindow("appAboveImeTarget"); mDisplayContent.setImeLayeringTarget(imeAppTarget); mDisplayContent.setImeControlTarget(imeAppTarget); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index dda5ea7aab53..3691fb06abdb 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9499,42 +9499,26 @@ public class CarrierConfigManager { private void addConfig(String key, Object value, PersistableBundle configs) { if (value instanceof String) { configs.putString(key, (String) value); - } - - if (value instanceof String[]) { + } else if (value instanceof String[]) { configs.putStringArray(key, (String[]) value); - } - - if (value instanceof Integer) { + } else if (value instanceof Integer) { configs.putInt(key, (Integer) value); - } - - if (value instanceof Long) { + } else if (value instanceof Long) { configs.putLong(key, (Long) value); - } - - if (value instanceof Double) { + } else if (value instanceof Double) { configs.putDouble(key, (Double) value); - } - - if (value instanceof Boolean) { + } else if (value instanceof Boolean) { configs.putBoolean(key, (Boolean) value); - } - - if (value instanceof int[]) { + } else if (value instanceof int[]) { configs.putIntArray(key, (int[]) value); - } - - if (value instanceof double[]) { + } else if (value instanceof double[]) { configs.putDoubleArray(key, (double[]) value); - } - - if (value instanceof boolean[]) { + } else if (value instanceof boolean[]) { configs.putBooleanArray(key, (boolean[]) value); - } - - if (value instanceof long[]) { + } else if (value instanceof long[]) { configs.putLongArray(key, (long[]) value); + } else if (value instanceof PersistableBundle) { + configs.putPersistableBundle(key, (PersistableBundle) value); } } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f3139a70eade..1b3a29d80bef 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -41,6 +41,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.WorkerThread; import android.app.PendingIntent; +import android.app.PropertyInvalidatedCache; import android.app.role.RoleManager; import android.compat.Compatibility; import android.compat.annotation.ChangeId; @@ -361,6 +362,42 @@ public class TelephonyManager { @GuardedBy("sCacheLock") private static final DeathRecipient sServiceDeath = new DeathRecipient(); + /** + * Cache key for a {@link PropertyInvalidatedCache} which maps from {@link PhoneAccountHandle} + * to subscription Id. The cache is initialized in {@code PhoneInterfaceManager}'s constructor + * when {@link PropertyInvalidatedCache#invalidateCache(String)} is called. + * The cache is cleared from {@code TelecomAccountRegistry#tearDown} when all phone accounts are + * removed from Telecom. + * @hide + */ + public static final String CACHE_KEY_PHONE_ACCOUNT_TO_SUBID = + "cache_key.telephony.phone_account_to_subid"; + private static final int CACHE_MAX_SIZE = 4; + + /** + * A {@link PropertyInvalidatedCache} which lives in an app's {@link TelephonyManager} instance. + * Caches any queries for a mapping between {@link PhoneAccountHandle} and {@code subscription + * id}. The cache may be invalidated from Telephony when phone account re-registration takes + * place. + */ + private PropertyInvalidatedCache<PhoneAccountHandle, Integer> mPhoneAccountHandleToSubIdCache = + new PropertyInvalidatedCache<PhoneAccountHandle, Integer>(CACHE_MAX_SIZE, + CACHE_KEY_PHONE_ACCOUNT_TO_SUBID) { + @Override + public Integer recompute(PhoneAccountHandle phoneAccountHandle) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getSubIdForPhoneAccountHandle(phoneAccountHandle, + mContext.getOpPackageName(), mContext.getAttributionTag()); + } + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + }; + /** Enum indicating multisim variants * DSDS - Dual SIM Dual Standby * DSDA - Dual SIM Dual Active @@ -11880,19 +11917,7 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) public int getSubscriptionId(@NonNull PhoneAccountHandle phoneAccountHandle) { - int retval = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - try { - ITelephony service = getITelephony(); - if (service != null) { - retval = service.getSubIdForPhoneAccountHandle( - phoneAccountHandle, mContext.getOpPackageName(), - mContext.getAttributionTag()); - } - } catch (RemoteException ex) { - Log.e(TAG, "getSubscriptionId RemoteException", ex); - ex.rethrowAsRuntimeException(); - } - return retval; + return mPhoneAccountHandleToSubIdCache.query(phoneAccountHandle); } /** @@ -14918,7 +14943,7 @@ public class TelephonyManager { } ITelephony service = getITelephony(); if (service != null) { - return service.isMvnoMatched(getSubId(), mvnoType, mvnoMatchData); + return service.isMvnoMatched(getSlotIndex(), mvnoType, mvnoMatchData); } } catch (RemoteException ex) { Rlog.e(TAG, "Telephony#matchesCurrentSimOperator RemoteException" + ex); diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index 4e85d8926f11..a846088cec6c 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -129,17 +129,31 @@ public abstract class QualifiedNetworksService extends Service { } /** - * Update the qualified networks list. Network availability provider must invoke this method - * whenever the qualified networks changes. If this method is never invoked for certain - * APN types, then frameworks will always use the default (i.e. cellular) data and network - * service. + * Update the suggested qualified networks list. Network availability provider must invoke + * this method whenever the suggested qualified networks changes. If this method is never + * invoked for certain APN types, then frameworks uses its own logic to determine the + * transport to setup the data network. + * + * For example, QNS can suggest frameworks to setup IMS on IWLAN by specifying + * {@link ApnSetting#TYPE_IMS} with a list containing single element + * {@link AccessNetworkType#IWLAN}. + * + * Or if QNS consider multiple access networks are qualified for certain APN type, it can + * suggest frameworks by specifying the APN type with multiple elements in the list like + * {{@link AccessNetworkType#EUTRAN}, {@link AccessNetworkType#IWLAN}}. Frameworks will then + * first attempt to setup data on LTE network. If the device moves from LTE to UMTS, then + * frameworks can perform handover the data network to the second preferred access network + * if available. + * + * If the {@code qualifiedNetworkTypes} list is empty, it means QNS has no suggestion to the + * frameworks, and frameworks will decide the transport to setup the data network. * * @param apnTypes APN types of the qualified networks. This must be a bitmask combination * of {@link ApnType}. * @param qualifiedNetworkTypes List of network types which are qualified for data * connection setup for {@link @apnType} in the preferred order. Each element in the list - * is a {@link AccessNetworkType}. An empty list indicates no networks are qualified - * for data setup. + * is a {@link AccessNetworkType}. Note that {@link AccessNetworkType#UNKNOWN} is not a + * valid input here. */ public final void updateQualifiedNetworkTypes( @ApnType int apnTypes, @NonNull List<Integer> qualifiedNetworkTypes) { diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index bce210fa22a0..5bae1ad72d93 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -621,9 +621,9 @@ public class ImsMmTelManager implements RegistrationManager { ITelephony iTelephony = getITelephony(); if (iTelephony == null) { - throw new RuntimeException("Could not find Telephony Service."); + Log.w("ImsMmTelManager", "Could not find Telephony Service."); + return; } - try { iTelephony.unregisterMmTelCapabilityCallback(mSubId, c.getBinder()); } catch (RemoteException e) { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index e3ebb9a23950..cfd940dac750 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2142,7 +2142,7 @@ interface ITelephony { List<RadioAccessSpecifier> getSystemSelectionChannels(int subId); - boolean isMvnoMatched(int subId, int mvnoType, String mvnoMatchData); + boolean isMvnoMatched(int slotIndex, int mvnoType, String mvnoMatchData); /** * Enqueue a pending sms Consumer, which will answer with the user specified selection for an diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt new file mode 100644 index 000000000000..2dbf304a0f23 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt @@ -0,0 +1,34 @@ +/* + * 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.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent + + class FixedOrientationAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy + ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt new file mode 100644 index 000000000000..2a296b7619c9 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt @@ -0,0 +1,113 @@ +/* + * 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.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.* +import com.android.server.wm.flicker.annotation.Group2 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window layer will become visible when switching from the fixed orientation activity. + * To run this test: `atest FlickerTests:OpenImeWindowFromFixedOrientationAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group2 +class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val fixedOrientationApp = FixedOrientationAppHelper(instrumentation) + private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + fixedOrientationApp.launchViaIntent(wmHelper) + this.setRotation(Surface.ROTATION_90) + } + } + transitions { + imeTestApp.launchViaIntent(wmHelper) + } + teardown { + test { + fixedOrientationApp.exit(wmHelper) + } + } + } + } + + @Postsubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Postsubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + @Postsubmit + @Test + fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() + + @Postsubmit + @Test + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + + @FlakyTest(bugId = 206753786) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + + @Postsubmit + @Test + fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 3, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt new file mode 100644 index 000000000000..6f2edd0bb221 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt @@ -0,0 +1,169 @@ +/* + * 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.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.* +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import org.junit.Assume.assumeTrue +import org.junit.Assume.assumeFalse +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window layer will be associated with the app task when going to the overview screen. + * To run this test: `atest FlickerTests:OpenImeWindowToOverViewTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + imeTestApp.launchViaIntent(wmHelper) + } + } + transitions { + device.pressRecentApps() + waitForRecentsActivityVisible(wmHelper) + } + teardown { + test { + device.pressHome() + imeTestApp.exit(wmHelper) + } + } + } + } + @Postsubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Postsubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + @Postsubmit + @Test + fun imeWindowIsAlwaysVisible() { + testSpec.imeWindowIsAlwaysVisible() + } + + @Postsubmit + @Test + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() + + @Postsubmit + @Test + fun statusBarLayerIsVisibleInPortrait() { + assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) + testSpec.statusBarLayerIsVisible() + } + + @Postsubmit + @Test + fun statusBarLayerIsInVisibleInLandscape() { + assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) + testSpec.assertLayersStart { + this.isVisible(FlickerComponentName.STATUS_BAR) + } + testSpec.assertLayersEnd { + this.isInvisible(FlickerComponentName.STATUS_BAR) + } + } + + @Postsubmit + @Test + fun imeLayerIsVisibleAndAssociatedWithAppWidow() { + testSpec.assertLayersStart { + isVisible(FlickerComponentName.IME).visibleRegion(FlickerComponentName.IME) + .coversAtMost(isVisible(imeTestApp.component) + .visibleRegion(imeTestApp.component).region) + } + testSpec.assertLayers { + this.invoke("imeLayerIsVisibleAndAlignAppWidow") { + val imeVisibleRegion = it.visibleRegion(FlickerComponentName.IME) + val appVisibleRegion = it.visibleRegion(imeTestApp.component) + if (imeVisibleRegion.region.isNotEmpty) { + it.isVisible(FlickerComponentName.IME) + imeVisibleRegion.coversAtMost(appVisibleRegion.region) + } + } + } + } + + private fun waitForRecentsActivityVisible( + wmHelper: WindowManagerStateHelper + ) { + val waitMsg = "state of Recents activity to be visible" + require( + wmHelper.waitFor(waitMsg) { + it.wmState.homeActivity?.let { act -> + it.wmState.isActivityVisible(act.name) + } == true || + it.wmState.recentsActivity?.let { act -> + it.wmState.isActivityVisible(act.name) + } == true + } + ) { "Recents activity should be visible" } + wmHelper.waitForAppTransitionIdle() + // Ensure WindowManagerService wait until all animations have completed + instrumentation.uiAutomation.syncInputTransactions() + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 1, + supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 9e371e5e381e..739fe020d555 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -108,5 +108,16 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".PortraitOnlyActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity" + android:theme="@style/CutoutShortEdges" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 13adb681c30c..3040a09f2345 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -60,4 +60,9 @@ public class ActivityOptions { public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".DialogThemedActivity"); + + public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity"; + public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".PortraitOnlyActivity"); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java new file mode 100644 index 000000000000..b1876b5e5511 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java @@ -0,0 +1,33 @@ +/* + * 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.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; +import android.view.WindowManager; + +public class PortraitOnlyActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.activity_simple); + } +} diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java index 72aa38dc7e4b..103d516e5967 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java @@ -97,13 +97,8 @@ public class SoundTriggerTestActivity extends Activity implements SoundTriggerTe setVolumeControlStream(AudioManager.STREAM_MUSIC); - // Make sure that the service is started, so even if our activity goes down, we'll still - // have a request for it to run. - startService(new Intent(getBaseContext(), SoundTriggerTestService.class)); - - // Bind to SoundTriggerTestService. - Intent intent = new Intent(this, SoundTriggerTestService.class); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, + AUDIO_PERMISSIONS_REQUEST); } @Override @@ -267,12 +262,16 @@ public class SoundTriggerTestActivity extends Activity implements SoundTriggerTe public synchronized void onCaptureAudioCheckboxClicked(View v) { // See if we have the right permissions - if (!mService.hasMicrophonePermission()) { - requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, - AUDIO_PERMISSIONS_REQUEST); - return; + if (mService == null) { + Log.e(TAG, "Can't set capture audio: not bound to SoundTriggerTestService"); } else { - mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); + if (!mService.hasMicrophonePermission()) { + requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, + AUDIO_PERMISSIONS_REQUEST); + return; + } else { + mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); + } } } @@ -283,8 +282,15 @@ public class SoundTriggerTestActivity extends Activity implements SoundTriggerTe if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { // Make sure that the check box is set to false. mCaptureAudioCheckBox.setChecked(false); + } else { + // After granted Record_Audio permission, start and bind the service. + // so we can run that sound trigger capability, + // even if our activity goes down, we'll still have a request for it to run. + startService(new Intent(getBaseContext(), SoundTriggerTestService.class)); + // Bind to SoundTriggerTestService. + Intent intent = new Intent(this, SoundTriggerTestService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } - mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked()); } } diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp index c9c6c5cf193b..77f98e88f1eb 100644 --- a/tests/TrustTests/Android.bp +++ b/tests/TrustTests/Android.bp @@ -25,6 +25,8 @@ android_test { "androidx.test.rules", "androidx.test.ext.junit", "androidx.test.uiautomator", + "mockito-target-minus-junit4", + "servicestests-utils", "truth-prebuilt", ], libs: [ diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt index 493f3bd22d2b..cf4965f1655d 100644 --- a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt +++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt @@ -41,7 +41,7 @@ abstract class BaseTrustAgentService : TrustAgentService() { private const val TAG = "BaseTrustAgentService" fun instance(serviceClass: KClass<out BaseTrustAgentService>): BaseTrustAgentService? { - return instances[serviceClass]!! + return instances[serviceClass] } } } diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt index af7a98c22ad1..f864fedf4e62 100644 --- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt +++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt @@ -16,6 +16,7 @@ package android.trust.test +import android.service.trust.GrantTrustResult import android.trust.BaseTrustAgentService import android.trust.TrustTestActivity import android.trust.test.lib.LockStateTrackingRule @@ -25,11 +26,13 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice +import com.android.server.testutils.mock import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.runner.RunWith +import org.mockito.Mockito.verifyZeroInteractions /** * Test for testing revokeTrust & grantTrust for non-renewable trust. @@ -66,7 +69,7 @@ class GrantAndRevokeTrustTest { @Test fun grantKeepsDeviceUnlocked() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {} uiDevice.sleep() lockStateTrackingRule.assertUnlocked() @@ -74,7 +77,7 @@ class GrantAndRevokeTrustTest { @Test fun grantKeepsDeviceUnlocked_untilRevoked() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0) + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0) {} await() uiDevice.sleep() trustAgentRule.agent.revokeTrust() @@ -82,6 +85,15 @@ class GrantAndRevokeTrustTest { lockStateTrackingRule.assertLocked() } + @Test + fun grantDoesNotCallBack() { + val callback = mock<(GrantTrustResult) -> Unit>() + trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback) + await() + + verifyZeroInteractions(callback) + } + companion object { private const val TAG = "GrantAndRevokeTrustTest" private const val GRANT_MESSAGE = "granted by test" diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt index a7dd41ad2e98..1194afa0123f 100644 --- a/tests/TrustTests/src/android/trust/test/LockUserTest.kt +++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt @@ -57,7 +57,6 @@ class LockUserTest { companion object { private const val TAG = "LockUserTest" - private fun await() = Thread.sleep(250) } } diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt index 14c227b1f678..3c6d54d24291 100644 --- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt +++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt @@ -16,16 +16,20 @@ package android.trust.test +import android.service.trust.GrantTrustResult +import android.service.trust.GrantTrustResult.STATUS_UNLOCKED_BY_GRANT import android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE import android.trust.BaseTrustAgentService import android.trust.TrustTestActivity import android.trust.test.lib.LockStateTrackingRule import android.trust.test.lib.ScreenLockRule import android.trust.test.lib.TrustAgentRule +import android.util.Log import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test @@ -70,7 +74,8 @@ class TemporaryAndRenewableTrustTest { uiDevice.sleep() lockStateTrackingRule.assertLocked() - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.wakeUp() lockStateTrackingRule.assertLocked() @@ -78,7 +83,8 @@ class TemporaryAndRenewableTrustTest { @Test fun grantTrustUnlockedDevice_deviceLocksOnScreenOff() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.sleep() lockStateTrackingRule.assertLocked() @@ -86,20 +92,48 @@ class TemporaryAndRenewableTrustTest { @Test fun grantTrustLockedDevice_grantTrustOnLockedDeviceUnlocksDevice() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.sleep() lockStateTrackingRule.assertLocked() - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.wakeUp() lockStateTrackingRule.assertUnlocked() } @Test + fun grantTrustLockedDevice_callsBackWhenUnlocked() { + Log.i(TAG, "Granting renewable trust while unlocked") + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + await(1000) + + Log.i(TAG, "Locking device") + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + + Log.i(TAG, "Renewing trust and unlocking") + var result: GrantTrustResult? = null + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) { + Log.i(TAG, "Callback received; status=${it.status}") + result = it + } + uiDevice.wakeUp() + lockStateTrackingRule.assertUnlocked() + + assertThat(result?.status).isEqualTo(STATUS_UNLOCKED_BY_GRANT) + } + + @Test fun grantTrustLockedDevice_revokeTrustPreventsSubsequentUnlock() { - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} uiDevice.sleep() lockStateTrackingRule.assertLocked() @@ -109,7 +143,8 @@ class TemporaryAndRenewableTrustTest { uiDevice.wakeUp() await(500) - trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} lockStateTrackingRule.assertLocked() } diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt index f8783fbaf121..8bd8340a04b1 100644 --- a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt +++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt @@ -32,7 +32,7 @@ import org.junit.rules.RuleChain import org.junit.runner.RunWith /** - * Test for testing the user unlock trigger. + * Test for the user unlock triggers. * * atest TrustTests:UserUnlockRequestTest */ @@ -53,13 +53,32 @@ class UserUnlockRequestTest { @Test fun reportUserRequestedUnlock_propagatesToAgent() { val oldCount = trustAgentRule.agent.onUserRequestedUnlockCallCount - trustManager.reportUserRequestedUnlock(userId) + trustManager.reportUserRequestedUnlock(userId, false) await() assertThat(trustAgentRule.agent.onUserRequestedUnlockCallCount) .isEqualTo(oldCount + 1) } + @Test + fun reportUserRequestedUnlock_propagatesToAgentWithDismissKeyguard() { + trustManager.reportUserRequestedUnlock(userId, true) + await() + + assertThat(trustAgentRule.agent.lastCallDismissKeyguard) + .isTrue() + } + + @Test + fun reportUserMayRequestUnlock_propagatesToAgent() { + val oldCount = trustAgentRule.agent.onUserMayRequestUnlockCallCount + trustManager.reportUserMayRequestUnlock(userId) + await() + + assertThat(trustAgentRule.agent.onUserMayRequestUnlockCallCount) + .isEqualTo(oldCount + 1) + } + companion object { private const val TAG = "UserUnlockRequestTest" private fun await() = Thread.sleep(250) @@ -69,10 +88,20 @@ class UserUnlockRequestTest { class UserUnlockRequestTrustAgent : BaseTrustAgentService() { var onUserRequestedUnlockCallCount: Long = 0 private set + var onUserMayRequestUnlockCallCount: Long = 0 + private set + var lastCallDismissKeyguard: Boolean = false + private set - override fun onUserRequestedUnlock() { - Log.i(TAG, "onUserRequestedUnlock") + override fun onUserRequestedUnlock(dismissKeyguard: Boolean) { + Log.i(TAG, "onUserRequestedUnlock($dismissKeyguard)") onUserRequestedUnlockCallCount++ + lastCallDismissKeyguard = dismissKeyguard + } + + override fun onUserMayRequestUnlock() { + Log.i(TAG, "onUserMayRequestUnlock") + onUserMayRequestUnlockCallCount++ } companion object { diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt index 834f2122a21b..2031af2cf0c9 100644 --- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt @@ -22,7 +22,6 @@ import android.content.Context import android.util.Log import android.view.WindowManagerGlobal import androidx.test.core.app.ApplicationProvider.getApplicationContext -import com.google.common.truth.Truth.assertThat import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement @@ -53,27 +52,12 @@ class LockStateTrackingRule : TestRule { } fun assertLocked() { - val maxWaits = 50 - var waitCount = 0 - - while ((lockState.locked == false) && waitCount < maxWaits) { - Log.i(TAG, "phone still locked, wait 50ms more ($waitCount)") - Thread.sleep(50) - waitCount++ - } - assertThat(lockState.locked).isTrue() + wait("un-locked per TrustListener") { lockState.locked == true } + wait("keyguard lock") { windowManager.isKeyguardLocked } } fun assertUnlocked() { - val maxWaits = 50 - var waitCount = 0 - - while ((lockState.locked == true) && waitCount < maxWaits) { - Log.i(TAG, "phone still unlocked, wait 50ms more ($waitCount)") - Thread.sleep(50) - waitCount++ - } - assertThat(lockState.locked).isFalse() + wait("locked per TrustListener") { lockState.locked == false } } inner class Listener : TrustListener { diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt index 006525d857ac..7eb8157d4b24 100644 --- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt @@ -42,7 +42,7 @@ class ScreenLockRule : TestRule { override fun apply(base: Statement, description: Description) = object : Statement() { override fun evaluate() { verifyNoScreenLockAlreadySet() - verifyKeyguardDismissed() + dismissKeyguard() setScreenLock() setLockOnPowerButton() @@ -51,7 +51,7 @@ class ScreenLockRule : TestRule { } finally { removeScreenLock() revertLockOnPowerButton() - verifyKeyguardDismissed() + dismissKeyguard() } } } @@ -62,19 +62,21 @@ class ScreenLockRule : TestRule { .isFalse() } - private fun verifyKeyguardDismissed() { - val maxWaits = 30 - var waitCount = 0 - - while (windowManager.isKeyguardLocked && waitCount < maxWaits) { - Log.i(TAG, "Keyguard still showing; attempting to dismiss and wait 50ms ($waitCount)") + fun dismissKeyguard() { + wait("keyguard dismissed") { count -> windowManager.dismissKeyguard(null, null) - Thread.sleep(50) - waitCount++ + + // Sometimes, bouncer gets shown due to a race, so we have to put display to sleep + // and wake it back up to get it to go away + if (count >= 10 && count % 5 == 0) { + Log.i(TAG, "Escalation: attempting screen off/on to get rid of bouncer") + uiDevice.sleep() + Thread.sleep(250) + uiDevice.wakeUp() + } + + !windowManager.isKeyguardLocked } - assertWithMessage("Keyguard should be unlocked") - .that(windowManager.isKeyguardLocked) - .isFalse() } private fun setScreenLock() { @@ -83,9 +85,7 @@ class ScreenLockRule : TestRule { LockscreenCredential.createNone(), context.userId ) - assertWithMessage("Screen Lock should now be set") - .that(lockPatternUtils.isSecure(context.userId)) - .isTrue() + wait("screen lock set") { lockPatternUtils.isSecure(context.userId) } Log.i(TAG, "Device PIN set to $PIN") } @@ -99,21 +99,16 @@ class ScreenLockRule : TestRule { LockscreenCredential.createNone(), LockscreenCredential.createPin(PIN), context.userId) - Thread.sleep(100) + Log.i(TAG, "Removing screen lock") assertWithMessage("Lock screen credential should be unset") .that(lockCredentialUnset) .isTrue() lockPatternUtils.setLockScreenDisabled(true, context.userId) - Thread.sleep(100) - assertWithMessage("Lockscreen needs to be disabled") - .that(lockPatternUtils.isLockScreenDisabled(context.userId)) - .isTrue() - - // this is here because somehow it helps the keyguard not get stuck - uiDevice.sleep() - Thread.sleep(500) // delay added to avoid initiating camera by double clicking power - uiDevice.wakeUp() + wait("screen lock un-set") { + lockPatternUtils.isLockScreenDisabled(context.userId) + } + wait("screen lock insecure") { !lockPatternUtils.isSecure(context.userId) } } private fun revertLockOnPowerButton() { diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt index 2a9e00276475..18bc029b6845 100644 --- a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt @@ -50,7 +50,6 @@ class TrustAgentRule<T : BaseTrustAgentService>( verifyTrustServiceRunning() unlockDeviceWithCredential() enableTrustAgent() - waitForEnablement() try { verifyAgentIsRunning() @@ -80,15 +79,10 @@ class TrustAgentRule<T : BaseTrustAgentService>( lockPatternUtils.setEnabledTrustAgents(agents, userId) } - private fun waitForEnablement() { - Log.d(TAG, "Waiting for $WAIT_TIME ms") - Thread.sleep(WAIT_TIME) - Log.d(TAG, "Done waiting") - } - private fun verifyAgentIsRunning() { - assertWithMessage("${serviceClass.simpleName} should be running") - .that(BaseTrustAgentService.instance(serviceClass)).isNotNull() + wait("${serviceClass.simpleName} to be running") { + BaseTrustAgentService.instance(serviceClass) != null + } } private fun disableTrustAgent() { @@ -112,6 +106,5 @@ class TrustAgentRule<T : BaseTrustAgentService>( TrustAgentRule(T::class) private const val TAG = "TrustAgentRule" - private val WAIT_TIME = 1000L } } diff --git a/tests/TrustTests/src/android/trust/test/lib/utils.kt b/tests/TrustTests/src/android/trust/test/lib/utils.kt new file mode 100644 index 000000000000..78140abec210 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/utils.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.trust.test.lib + +import android.util.Log +import com.google.common.truth.Truth.assertWithMessage + +private const val TAG = "TrustTestUtils" + +/** + * Waits for [conditionFunction] to be true with a failed assertion if it is not after [maxWait] + * ms. + * + * The condition function can perform additional logic (for example, logging or attempting to make + * the condition become true). + * + * @param conditionFunction function which takes the attempt count & returns whether the condition + * is met + */ +internal fun wait( + description: String? = null, + maxWait: Long = 1500L, + rate: Long = 50L, + conditionFunction: (count: Int) -> Boolean +) { + var waited = 0L + var count = 0 + while (!conditionFunction.invoke(count)) { + assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description") + .that(waited <= maxWait) + .isTrue() + waited += rate + count++ + Log.i(TAG, "Waiting for $description ($waited/$maxWait) #$count") + Thread.sleep(rate) + } +} diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp index 3e1297190622..6efd1f64d8fe 100644 --- a/tools/locked_region_code_injection/Android.bp +++ b/tools/locked_region_code_injection/Android.bp @@ -12,10 +12,10 @@ java_binary_host { manifest: "manifest.txt", srcs: ["src/**/*.java"], static_libs: [ - "asm-7.0", - "asm-commons-7.0", - "asm-tree-7.0", - "asm-analysis-7.0", + "asm-9.2", + "asm-commons-9.2", + "asm-tree-9.2", + "asm-analysis-9.2", "guava-21.0", ], } diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp index ec2bffdfaf57..6ebacd8a0b14 100644 --- a/tools/sdkparcelables/Android.bp +++ b/tools/sdkparcelables/Android.bp @@ -14,7 +14,7 @@ java_binary_host { "src/**/*.kt", ], static_libs: [ - "asm-7.0", + "asm-9.2", ], } diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp index 1395c5f2e635..39d1b1c2defd 100644 --- a/tools/traceinjection/Android.bp +++ b/tools/traceinjection/Android.bp @@ -12,10 +12,10 @@ java_binary_host { manifest: "manifest.txt", srcs: ["src/**/*.java"], static_libs: [ - "asm-7.0", - "asm-commons-7.0", - "asm-tree-7.0", - "asm-analysis-7.0", + "asm-9.2", + "asm-commons-9.2", + "asm-tree-9.2", + "asm-analysis-9.2", "guava-21.0", ], } |