diff options
101 files changed, 3214 insertions, 924 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 31c0e30928b8..d18a9c7da5a1 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -287,6 +287,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setDreamOverlay(@Nullable android.content.ComponentName); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setScreensaverEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setSystemDreamComponent(@Nullable android.content.ComponentName); + method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@Nullable android.content.ComponentName); method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(); method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream(); } diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java index 7c8b0fd499e3..3bb66581d502 100644 --- a/core/java/android/app/DreamManager.java +++ b/core/java/android/app/DreamManager.java @@ -87,6 +87,25 @@ public class DreamManager { /** * Starts dreaming. * + * This API is equivalent to {@link DreamManager#startDream()} but with a nullable component + * name to be compatible with TM CTS tests. + * + * <p>This is only used for testing the dream service APIs. + * + * @see DreamManager#startDream() + * + * @hide + */ + @TestApi + @UserHandleAware + @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) + public void startDream(@Nullable ComponentName name) { + startDream(); + } + + /** + * Starts dreaming. + * * The system dream component, if set by {@link DreamManager#setSystemDreamComponent}, will be * started. * Otherwise, starts the active dream set by {@link DreamManager#setActiveDream}. diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 13934e592d40..a51b9d3956df 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -391,7 +391,12 @@ public class PropertyInvalidatedCache<Query, Result> { private static final boolean DEBUG = false; private static final boolean VERIFY = false; - // Per-Cache performance counters. As some cache instances are declared static, + /** + * The object-private lock. + */ + private final Object mLock = new Object(); + + // Per-Cache performance counters. @GuardedBy("mLock") private long mHits = 0; @@ -410,25 +415,19 @@ public class PropertyInvalidatedCache<Query, Result> { @GuardedBy("mLock") private long mClears = 0; - // Most invalidation is done in a static context, so the counters need to be accessible. - @GuardedBy("sCorkLock") - private static final HashMap<String, Long> sInvalidates = new HashMap<>(); - /** - * Record the number of invalidate or cork calls that were nops because - * the cache was already corked. This is static because invalidation is - * done in a static context. + * Protect objects that support corking. mLock and sGlobalLock must never be taken while this + * is held. */ - @GuardedBy("sCorkLock") - private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>(); + private static final Object sCorkLock = new Object(); /** - * If sEnabled is false then all cache operations are stubbed out. Set - * it to false inside test processes. + * Record the number of invalidate or cork calls that were nops because the cache was already + * corked. This is static because invalidation is done in a static context. Entries are + * indexed by the cache property. */ - private static boolean sEnabled = true; - - private static final Object sCorkLock = new Object(); + @GuardedBy("sCorkLock") + private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>(); /** * A map of cache keys that we've "corked". (The values are counts.) When a cache key is @@ -440,21 +439,39 @@ public class PropertyInvalidatedCache<Query, Result> { private static final HashMap<String, Integer> sCorks = new HashMap<>(); /** + * A lock for the global list of caches and cache keys. This must never be taken inside mLock + * or sCorkLock. + */ + private static final Object sGlobalLock = new Object(); + + /** * A map of cache keys that have been disabled in the local process. When a key is * disabled locally, existing caches are disabled and the key is saved in this map. * Future cache instances that use the same key will be disabled in their constructor. */ - @GuardedBy("sCorkLock") + @GuardedBy("sGlobalLock") private static final HashSet<String> sDisabledKeys = new HashSet<>(); /** * Weakly references all cache objects in the current process, allowing us to iterate over * them all for purposes like issuing debug dumps and reacting to memory pressure. */ - @GuardedBy("sCorkLock") + @GuardedBy("sGlobalLock") private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>(); - private final Object mLock = new Object(); + /** + * Counts of the number of times a cache key was invalidated. Invalidation occurs in a static + * context with no cache object available, so this is a static map. Entries are indexed by + * the cache property. + */ + @GuardedBy("sGlobalLock") + private static final HashMap<String, Long> sInvalidates = new HashMap<>(); + + /** + * If sEnabled is false then all cache operations are stubbed out. Set + * it to false inside test processes. + */ + private static boolean sEnabled = true; /** * Name of the property that holds the unique value that we use to invalidate the cache. @@ -595,14 +612,17 @@ public class PropertyInvalidatedCache<Query, Result> { }; } - // Register the map in the global list. If the cache is disabled globally, disable it - // now. + /** + * Register the map in the global list. If the cache is disabled globally, disable it + * now. This method is only ever called from the constructor, which means no other thread has + * access to the object yet. It can safely be modified outside any lock. + */ private void registerCache() { - synchronized (sCorkLock) { - sCaches.put(this, null); + synchronized (sGlobalLock) { if (sDisabledKeys.contains(mCacheName)) { disableInstance(); } + sCaches.put(this, null); } } @@ -797,8 +817,9 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Disable the use of this cache in this process. This method is using during - * testing. To disable a cache in normal code, use disableLocal(). + * Disable the use of this cache in this process. This method is using internally and during + * testing. To disable a cache in normal code, use disableLocal(). A disabled cache cannot + * be re-enabled. * @hide */ @TestApi @@ -811,17 +832,23 @@ public class PropertyInvalidatedCache<Query, Result> { /** * Disable the local use of all caches with the same name. All currently registered caches - * using the key will be disabled now, and all future cache instances that use the key will be - * disabled in their constructor. + * with the name will be disabled now, and all future cache instances that use the name will + * be disabled in their constructor. */ private static final void disableLocal(@NonNull String name) { - synchronized (sCorkLock) { - sDisabledKeys.add(name); + synchronized (sGlobalLock) { + if (sDisabledKeys.contains(name)) { + // The key is already in recorded so there is no further work to be done. + return; + } for (PropertyInvalidatedCache cache : sCaches.keySet()) { if (name.equals(cache.mCacheName)) { cache.disableInstance(); } } + // Record the disabled key after the iteration. If an exception occurs during the + // iteration above, and the code is retried, the function should not exit early. + sDisabledKeys.add(name); } } @@ -834,7 +861,7 @@ public class PropertyInvalidatedCache<Query, Result> { */ @TestApi public final void forgetDisableLocal() { - synchronized (sCorkLock) { + synchronized (sGlobalLock) { sDisabledKeys.remove(mCacheName); } } @@ -851,9 +878,9 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Disable this cache in the current process, and all other caches that use the same - * name. This does not affect caches that have a different name but use the same - * property. + * Disable this cache in the current process, and all other present and future caches that use + * the same name. This does not affect caches that have a different name but use the same + * property. Once disabled, a cache cannot be reenabled. * @hide */ @TestApi @@ -1381,10 +1408,9 @@ public class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of caches alive at the current time. */ + @GuardedBy("sGlobalLock") private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() { - synchronized (sCorkLock) { - return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); - } + return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); } /** @@ -1540,7 +1566,7 @@ public class PropertyInvalidatedCache<Query, Result> { boolean detail = anyDetailed(args); ArrayList<PropertyInvalidatedCache> activeCaches; - synchronized (sCorkLock) { + synchronized (sGlobalLock) { activeCaches = getActiveCaches(); if (!detail) { dumpCorkInfo(pw); diff --git a/core/java/android/os/PackageTagsList.java b/core/java/android/os/PackageTagsList.java index 4a81dc6f592e..df99074a851f 100644 --- a/core/java/android/os/PackageTagsList.java +++ b/core/java/android/os/PackageTagsList.java @@ -26,6 +26,7 @@ import android.util.ArraySet; import com.android.internal.annotations.Immutable; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Objects; @@ -140,6 +141,17 @@ public final class PackageTagsList implements Parcelable { return true; } + /** + * Returns a list of packages. + * + * @deprecated Do not use. + * @hide + */ + @Deprecated + public @NonNull Collection<String> getPackages() { + return new ArrayList<>(mPackageTags.keySet()); + } + public static final @NonNull Parcelable.Creator<PackageTagsList> CREATOR = new Parcelable.Creator<PackageTagsList>() { @SuppressWarnings("unchecked") @@ -217,7 +229,7 @@ public final class PackageTagsList implements Parcelable { if (j > 0) { pw.print(", "); } - if (attributionTag.startsWith(packageName)) { + if (attributionTag != null && attributionTag.startsWith(packageName)) { pw.print(attributionTag.substring(packageName.length())); } else { pw.print(attributionTag); diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 267b2ff818a6..4d33bfd1e94a 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -91,6 +91,12 @@ public final class Condition implements Parcelable { public final int icon; /** + * The maximum string length for any string contained in this condition. + * @hide + */ + public static final int MAX_STRING_LENGTH = 1000; + + /** * An object representing the current state of a {@link android.app.AutomaticZenRule}. * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule * @param summary a user visible description of the rule state. @@ -104,16 +110,19 @@ public final class Condition implements Parcelable { if (id == null) throw new IllegalArgumentException("id is required"); if (summary == null) throw new IllegalArgumentException("summary is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); - this.id = id; - this.summary = summary; - this.line1 = line1; - this.line2 = line2; + this.id = getTrimmedUri(id); + this.summary = getTrimmedString(summary); + this.line1 = getTrimmedString(line1); + this.line2 = getTrimmedString(line2); this.icon = icon; this.state = state; this.flags = flags; } public Condition(Parcel source) { + // This constructor passes all fields directly into the constructor that takes all the + // fields as arguments; that constructor will trim each of the input strings to + // max length if necessary. this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class), source.readString(), source.readString(), @@ -240,4 +249,25 @@ public final class Condition implements Parcelable { return new Condition[size]; } }; + + /** + * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. + */ + private static String getTrimmedString(String input) { + if (input != null && input.length() > MAX_STRING_LENGTH) { + return input.substring(0, MAX_STRING_LENGTH); + } + return input; + } + + /** + * Returns a truncated copy of the Uri by trimming the string representation to the maximum + * string length. + */ + private static Uri getTrimmedUri(Uri input) { + if (input != null && input.toString().length() > MAX_STRING_LENGTH) { + return Uri.parse(getTrimmedString(input.toString())); + } + return input; + } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 285a40779b90..fadad99f0885 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -709,7 +709,7 @@ public class Editor { } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); - resumeBlink(); + makeBlink(); } void onDetachedFromWindow() { @@ -1685,17 +1685,12 @@ public class Editor { void onWindowFocusChanged(boolean hasWindowFocus) { if (hasWindowFocus) { - if (mBlink != null) { - mBlink.uncancel(); - makeBlink(); - } + resumeBlink(); if (mTextView.hasSelection() && !extractedTextModeWillBeStarted()) { refreshTextActionMode(); } } else { - if (mBlink != null) { - mBlink.cancel(); - } + suspendBlink(); if (mInputContentType != null) { mInputContentType.enterDown = false; } @@ -2851,7 +2846,8 @@ public class Editor { * @return True when the TextView isFocused and has a valid zero-length selection (cursor). */ private boolean shouldBlink() { - if (!isCursorVisible() || !mTextView.isFocused()) return false; + if (!isCursorVisible() || !mTextView.isFocused() + || mTextView.getWindowVisibility() != mTextView.VISIBLE) return false; final int start = mTextView.getSelectionStart(); if (start < 0) return false; @@ -2873,6 +2869,17 @@ public class Editor { } } + /** + * + * @return whether the Blink runnable is blinking or not, if null return false. + * @hide + */ + @VisibleForTesting + public boolean isBlinking() { + if (mBlink == null) return false; + return !mBlink.mCancelled; + } + private class Blink implements Runnable { private boolean mCancelled; diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 8ca763e81757..33ea2e4af473 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -110,11 +110,11 @@ public final class TransitionInfo implements Parcelable { /** The container is an input-method window. */ public static final int FLAG_IS_INPUT_METHOD = 1 << 8; - /** The container is ActivityEmbedding embedded. */ - public static final int FLAG_IS_EMBEDDED = 1 << 9; + /** The container is in a Task with embedded activity. */ + public static final int FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY = 1 << 9; - /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 10; + /** The container fills its parent Task before and after the transition. */ + public static final int FLAG_FILLS_TASK = 1 << 10; /** The container is going to show IME on its task after the transition. */ public static final int FLAG_WILL_IME_SHOWN = 1 << 11; @@ -125,6 +125,9 @@ public final class TransitionInfo implements Parcelable { /** The container attaches work profile thumbnail for cross profile animation. */ public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ + public static final int FLAG_FIRST_CUSTOM = 1 << 14; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, @@ -137,9 +140,12 @@ public final class TransitionInfo implements Parcelable { FLAG_OCCLUDES_KEYGUARD, FLAG_DISPLAY_HAS_ALERT_WINDOWS, FLAG_IS_INPUT_METHOD, - FLAG_IS_EMBEDDED, - FLAG_FIRST_CUSTOM, - FLAG_WILL_IME_SHOWN + FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, + FLAG_FILLS_TASK, + FLAG_WILL_IME_SHOWN, + FLAG_CROSS_PROFILE_OWNER_THUMBNAIL, + FLAG_CROSS_PROFILE_WORK_THUMBNAIL, + FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -322,28 +328,31 @@ public final class TransitionInfo implements Parcelable { sb.append("IS_INPUT_METHOD"); } if ((flags & FLAG_TRANSLUCENT) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "TRANSLUCENT"); + sb.append(sb.length() == 0 ? "" : "|").append("TRANSLUCENT"); } if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "STARTING_WINDOW_TRANSFER"); + sb.append(sb.length() == 0 ? "" : "|").append("STARTING_WINDOW_TRANSFER"); } if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION"); + sb.append(sb.length() == 0 ? "" : "|").append("IS_VOICE_INTERACTION"); } if ((flags & FLAG_IS_DISPLAY) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY"); + sb.append(sb.length() == 0 ? "" : "|").append("IS_DISPLAY"); } if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD"); + sb.append(sb.length() == 0 ? "" : "|").append("OCCLUDES_KEYGUARD"); } if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS"); + sb.append(sb.length() == 0 ? "" : "|").append("DISPLAY_HAS_ALERT_WINDOWS"); + } + if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY"); } - if ((flags & FLAG_IS_EMBEDDED) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "IS_EMBEDDED"); + if ((flags & FLAG_FILLS_TASK) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK"); } if ((flags & FLAG_FIRST_CUSTOM) != 0) { - sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM"); + sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } return sb.toString(); } diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java new file mode 100644 index 000000000000..42629ba41287 --- /dev/null +++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +import android.net.Uri; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConditionTest { + private static final String CLASS = "android.service.notification.Condition"; + + @Test + public void testLongFields_inConstructors() { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + + // Confirm strings are truncated via short constructor + Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE); + + assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length()); + + // Confirm strings are truncated via long constructor + Condition cond2 = new Condition(longUri, longString, longString, longString, + -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS); + + assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length()); + } + + @Test + public void testLongFields_viaParcel() { + // Set fields via reflection to force them to be long, then parcel and unparcel to make sure + // it gets truncated upon unparcelling. + Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder", + Condition.STATE_TRUE); + + try { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + Field id = Class.forName(CLASS).getDeclaredField("id"); + id.setAccessible(true); + id.set(cond, longUri); + Field summary = Class.forName(CLASS).getDeclaredField("summary"); + summary.setAccessible(true); + summary.set(cond, longString); + Field line1 = Class.forName(CLASS).getDeclaredField("line1"); + line1.setAccessible(true); + line1.set(cond, longString); + Field line2 = Class.forName(CLASS).getDeclaredField("line2"); + line2.setAccessible(true); + line2.set(cond, longString); + } catch (NoSuchFieldException e) { + fail(e.toString()); + } catch (ClassNotFoundException e) { + fail(e.toString()); + } catch (IllegalAccessException e) { + fail(e.toString()); + } + + Parcel parcel = Parcel.obtain(); + cond.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Condition fromParcel = new Condition(parcel); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length()); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index e0004fcaa060..521a65cc4df6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -16,7 +16,8 @@ package com.android.wm.shell.activityembedding; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static java.util.Objects.requireNonNull; @@ -84,12 +85,23 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - // TODO(b/207070762) Handle AE animation as a part of other transitions. - // Only handle the transition if all containers are embedded. + boolean containsEmbeddingSplit = false; for (TransitionInfo.Change change : info.getChanges()) { - if (!isEmbedded(change)) { + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + // Only animate the transition if all changes are in a Task with ActivityEmbedding. return false; } + if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) { + // Whether the Task contains any ActivityEmbedding split before or after the + // transition. + containsEmbeddingSplit = true; + } + } + if (!containsEmbeddingSplit) { + // Let the system to play the default animation if there is no ActivityEmbedding split + // window. This allows to play the app customized animation when there is no embedding, + // such as the device is in a folded state. + return false; } // Start ActivityEmbedding animation. @@ -119,8 +131,4 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle } callback.onTransitionFinished(null /* wct */, null /* wctCB */); } - - private static boolean isEmbedded(@NonNull TransitionInfo.Change change) { - return (change.getFlags() & FLAG_IS_EMBEDDED) != 0; - } } 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 aeaf6eda9809..be100bb1dd34 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 @@ -1270,7 +1270,7 @@ public class BubbleStackView extends FrameLayout } final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) - && mExpandedBubble != null; + && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null; if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show manage edu: " + shouldShow); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt index 063dac3d4109..ab194dfb3ce9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt @@ -24,8 +24,8 @@ import android.util.IntProperty import android.view.Gravity import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.view.WindowInsets +import android.view.WindowManager import android.widget.FrameLayout import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY @@ -41,6 +41,7 @@ class DismissView(context: Context) : FrameLayout(context) { var circle = DismissCircleView(context) var isShowing = false + var targetSizeResId: Int private val animator = PhysicsAnimator.getInstance(circle) private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) @@ -70,7 +71,8 @@ class DismissView(context: Context) : FrameLayout(context) { setVisibility(View.INVISIBLE) setBackgroundDrawable(gradientDrawable) - val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + targetSizeResId = R.dimen.dismiss_circle_size + val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId) addView(circle, LayoutParams(targetSize, targetSize, Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)) // start with circle offscreen so it's animated up @@ -126,7 +128,7 @@ class DismissView(context: Context) : FrameLayout(context) { layoutParams.height = resources.getDimensionPixelSize( R.dimen.floating_dismiss_gradient_height) - val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + val targetSize = resources.getDimensionPixelSize(targetSizeResId) circle.layoutParams.width = targetSize circle.layoutParams.height = targetSize circle.requestLayout() @@ -153,4 +155,4 @@ class DismissView(context: Context) : FrameLayout(context) { setPadding(0, 0, 0, navInset.bottom + resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index b0080b24c609..e7ec15e70c11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -45,11 +45,6 @@ class MainStage extends StageTaskListener { iconProvider); } - @Override - void dismiss(WindowContainerTransaction wct, boolean toTop) { - deactivate(wct, toTop); - } - boolean isActive() { return mIsActive; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 86efbe0af79c..8639b36faf4c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -42,11 +42,6 @@ class SideStage extends StageTaskListener { iconProvider); } - @Override - void dismiss(WindowContainerTransaction wct, boolean toTop) { - removeAllTasks(wct, toTop); - } - boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { if (mChildrenTaskInfo.size() == 0) return false; wct.reparentTasks( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 946dfdecc5d4..db35b48eb898 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -123,6 +123,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; +import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.SplitBounds; @@ -203,6 +204,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + private DefaultMixedHandler mMixedHandler; + private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = new SplitWindowManager.ParentContainerCallbacks() { @Override @@ -326,6 +329,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, transitions.addHandler(this); } + public void setMixedHandler(DefaultMixedHandler mixedHandler) { + mMixedHandler = mixedHandler; + } + @VisibleForTesting SplitScreenTransitions getSplitTransitions() { return mSplitTransitions; @@ -927,13 +934,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Expand to top side split as full screen for fading out decor animation and dismiss // another side split(Moving its children to bottom). mIsExiting = true; - final StageTaskListener tempFullStage = childrenToTop; - final StageTaskListener dismissStage = mMainStage == childrenToTop - ? mSideStage : mMainStage; - tempFullStage.resetBounds(wct); - wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token, + childrenToTop.resetBounds(wct); + wct.reorder(childrenToTop.mRootTaskInfo.token, true); + wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); - dismissStage.dismiss(wct, false /* toTop */); } mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { @@ -950,7 +954,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, childrenToTop.fadeOutDecor(() -> { WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); mIsExiting = false; - childrenToTop.dismiss(finishedWCT, true /* toTop */); + mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */); + mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); @@ -1879,8 +1884,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Use normal animations. return false; + } else if (mMixedHandler != null && hasDisplayChange(info)) { + // A display-change has been un-expectedly inserted into the transition. Redirect + // handling to the mixed-handler to deal with splitting it up. + if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, + startTransaction, finishTransaction, finishCallback)) { + return true; + } } + return startPendingAnimation(transition, info, startTransaction, finishTransaction, + finishCallback); + } + + /** Starts the pending transition animation. */ + public boolean startPendingAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean shouldAnimate = true; if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation( @@ -1899,6 +1921,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + private boolean hasDisplayChange(TransitionInfo info) { + boolean has = false; + for (int iC = 0; iC < info.getChanges().size() && !has; ++iC) { + final TransitionInfo.Change change = info.getChanges().get(iC); + has = change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0; + } + return has; + } + /** Called to clean-up state and do house-keeping after the animation is done. */ public void onTransitionAnimationComplete() { // If still playing, let it finish. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 1af9415fca3a..6b90eabe3bd2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -106,11 +106,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } - /** - * General function for dismiss this stage. - */ - void dismiss(WindowContainerTransaction wct, boolean toTop) {} - int getChildCount() { return mChildrenTaskInfo.size(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index bcf4fbda0e0c..3cba92956f95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -17,6 +17,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -57,6 +58,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; + /** Both the display and split-state (enter/exit) is changing */ + static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -69,6 +73,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { Transitions.TransitionFinishCallback mFinishCallback = null; Transitions.TransitionHandler mLeftoversHandler = null; + WindowContainerTransaction mFinishWCT = null; /** * Mixed transitions are made up of multiple "parts". This keeps track of how many @@ -95,6 +100,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler(); mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler(); mPlayer.addHandler(this); + if (mSplitHandler != null) { + mSplitHandler.setMixedHandler(this); + } }, this); } } @@ -122,10 +130,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } private TransitionInfo subCopy(@NonNull TransitionInfo info, - @WindowManager.TransitionType int newType) { - final TransitionInfo out = new TransitionInfo(newType, info.getFlags()); - for (int i = 0; i < info.getChanges().size(); ++i) { - out.getChanges().add(info.getChanges().get(i)); + @WindowManager.TransitionType int newType, boolean withChanges) { + final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); + if (withChanges) { + for (int i = 0; i < info.getChanges().size(); ++i) { + out.getChanges().add(info.getChanges().get(i)); + } } out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y); out.setAnimationOptions(info.getAnimationOptions()); @@ -157,6 +167,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { + return false; } else { mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " @@ -173,7 +185,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { + "entering PIP while Split-Screen is active."); TransitionInfo.Change pipChange = null; TransitionInfo.Change wallpaper = null; - final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK); + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); boolean homeIsOpening = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { TransitionInfo.Change change = info.getChanges().get(i); @@ -254,6 +266,87 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return true; } + private void unlinkMissingParents(TransitionInfo from) { + for (int i = 0; i < from.getChanges().size(); ++i) { + final TransitionInfo.Change chg = from.getChanges().get(i); + if (chg.getParent() == null) continue; + if (from.getChange(chg.getParent()) == null) { + from.getChanges().get(i).setParent(null); + } + } + } + + private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) { + TransitionInfo.Change curr = chg; + while (curr != null) { + if (curr.getTaskInfo() != null) return true; + if (curr.getParent() == null) break; + curr = info.getChange(curr.getParent()); + } + return false; + } + + /** + * This is intended to be called by SplitCoordinator as a helper to mix an already-pending + * split transition with a display-change. The use-case for this is when a display + * change/rotation gets collected into a split-screen enter/exit transition which has already + * been claimed by StageCoordinator.handleRequest . This happens during launcher tests. + */ + public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */); + final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (isWithinTask(info, change)) continue; + displayPart.addChange(change); + everythingElse.getChanges().remove(i); + } + if (displayPart.getChanges().isEmpty()) return false; + unlinkMissingParents(everythingElse); + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition); + mixed.mFinishCallback = finishCallback; + mActiveTransitions.add(mixed); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change " + + "and split change."); + // We need to split the transition into 2 parts: the split part and the display part. + mixed.mInFlightSubAnimations = 2; + + Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + --mixed.mInFlightSubAnimations; + if (wctCB != null) { + throw new IllegalArgumentException("Can't mix transitions that require finish" + + " sync callback"); + } + if (wct != null) { + if (mixed.mFinishWCT == null) { + mixed.mFinishWCT = wct; + } else { + mixed.mFinishWCT.merge(wct, true /* transfer */); + } + } + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */); + }; + + // Dispatch the display change. This will most-likely be taken by the default handler. + // Do this first since the first handler used will apply the startT; the display change + // needs to take a screenshot before that happens so we need it to be the first handler. + mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart, + startT, finishT, finishCB, mSplitHandler); + + // Note: at this point, startT has probably already been applied, so we are basically + // giving splitHandler an empty startT. This is currently OK because display-change will + // grab a screenshot and paste it on top anyways. + mSplitHandler.startPendingAnimation( + transition, everythingElse, startT, finishT, finishCB); + return true; + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -279,6 +372,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else { mPipHandler.end(); } + } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { + // queue } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index b2e45a6b3a5c..a7234c1d3cb8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -17,7 +17,7 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -57,7 +57,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim public void testStartAnimation() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); info.addChange(embeddingChange); doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index 84befdddabdb..3792e8361284 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -16,6 +16,9 @@ package com.android.wm.shell.activityembedding; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertNotNull; @@ -24,6 +27,8 @@ import static org.mockito.Mockito.mock; import android.animation.Animator; import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -80,4 +85,23 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { return new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); } + + /** + * Creates a mock {@link TransitionInfo.Change} with + * {@link TransitionInfo#FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY} flag. + */ + static TransitionInfo.Change createEmbeddedChange(@NonNull Rect startBounds, + @NonNull Rect endBounds, @NonNull Rect taskBounds) { + final TransitionInfo.Change change = createChange(); + change.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + change.setStartAbsBounds(startBounds); + change.setEndAbsBounds(endBounds); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + change.setFlags(FLAG_FILLS_TASK); + } + return change; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index cf43b0030d2a..baecf6fe6673 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -17,7 +17,6 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -29,6 +28,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.graphics.Rect; import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -48,6 +48,10 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase { + private static final Rect TASK_BOUNDS = new Rect(0, 0, 1000, 500); + private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500); + private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500); + @Before public void setup() { super.setUp(); @@ -77,13 +81,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Test public void testStartAnimation_containsNonActivityEmbeddingChange() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); final TransitionInfo.Change nonEmbeddingChange = createChange(); info.addChange(embeddingChange); info.addChange(nonEmbeddingChange); - // No-op + // No-op because it contains non-embedded change. assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); @@ -93,13 +97,65 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation } @Test - public void testStartAnimation_onlyActivityEmbeddingChange() { + public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS, + TASK_BOUNDS); + info.addChange(embeddingChange); + + // No-op because it only contains embedded change that fills the Task. We will let the + // default handler to animate such transition. + assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); + verifyNoMoreInteractions(mStartTransaction); + verifyNoMoreInteractions(mFinishTransaction); + verifyNoMoreInteractions(mFinishCallback); + } + + @Test + public void testStartAnimation_containsActivityEmbeddingSplitChange() { + // Change that occupies only part of the Task. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() { + // Change that is entering ActivityEmbedding split. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() { + // Change that is exiting ActivityEmbedding split. final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, + TASK_BOUNDS, TASK_BOUNDS); info.addChange(embeddingChange); - // No-op + // ActivityEmbeddingController will handle such transition. assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, @@ -115,8 +171,8 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation () -> mController.onAnimationFinished(mTransition)); final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); info.addChange(embeddingChange); mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index ea9390ee8efd..9240abfbe47f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -225,7 +225,6 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME); verify(mMainStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); - verify(mSideStage).dismiss(any(WindowContainerTransaction.class), eq(false)); verify(mMainStage).resetBounds(any(WindowContainerTransaction.class)); } @@ -239,7 +238,6 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSideStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); verify(mSideStage).resetBounds(any(WindowContainerTransaction.class)); - verify(mMainStage).dismiss(any(WindowContainerTransaction.class), eq(false)); } @Test diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index f4e965f422c0..3114be0e9c65 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -141,4 +141,5 @@ interface ILocationManager // used by gts tests to verify whitelists String[] getBackgroundThrottlingWhitelist(); PackageTagsList getIgnoreSettingsAllowlist(); + PackageTagsList getAdasAllowlist(); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 371f5ed4f346..5fe4ffd869ce 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -504,6 +504,19 @@ public class LocationManager { } /** + * Returns ADAS packages and their associated attribution tags. + * + * @hide + */ + public @NonNull PackageTagsList getAdasAllowlist() { + try { + return mService.getAdasAllowlist(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the extra location controller package on the device. * * @hide diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 7d4dcf88542b..ebabdf571dfd 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -352,8 +352,11 @@ class ActivityLaunchAnimator( * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called * before the cancellation. + * + * If this launch animation affected the occlusion state of the keyguard, WM will provide + * us with [newKeyguardOccludedState] so that we can set the occluded state appropriately. */ - fun onLaunchAnimationCancelled() {} + fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} } @VisibleForTesting @@ -667,7 +670,7 @@ class ActivityLaunchAnimator( removeTimeout() context.mainExecutor.execute { animation?.cancel() - controller.onLaunchAnimationCancelled() + controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded) } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index eac5d275092a..9656b8a99d41 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -238,7 +238,7 @@ constructor( } } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { controller.onLaunchAnimationCancelled() enableDialogDismiss() dialog.dismiss() diff --git a/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml b/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml new file mode 100644 index 000000000000..732fc57dbded --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml @@ -0,0 +1,43 @@ +<?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. + --> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="6dp" + android:insetBottom="6dp"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="?android:attr/textColorSecondary"/> + <corners android:radius="24dp"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="24dp"/> + <solid android:color="@android:color/transparent"/> + <stroke android:color="?androidprv:attr/colorAccentTertiary" + android:width="1dp" + /> + <padding android:left="16dp" + android:top="12dp" + android:right="16dp" + android:bottom="12dp"/> + </shape> + </item> + </ripple> +</inset> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml index db508c91e0de..67fd5b9a78bd 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml @@ -17,15 +17,17 @@ */ --> -<!-- This contains disable esim buttonas shared by sim_pin/sim_puk screens --> -<com.android.keyguard.KeyguardEsimArea - xmlns:android="http://schemas.android.com/apk/res/android" +<!-- This contains disable eSim buttons shared by sim_pin/sim_puk screens --> +<com.android.keyguard.KeyguardEsimArea xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_disable_esim" + style="@style/Keyguard.TextView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:id="@+id/keyguard_disable_esim" - android:visibility="gone" + android:background="@drawable/kg_bouncer_secondary_button" + android:drawablePadding="10dp" + android:drawableStart="@drawable/ic_no_sim" + android:drawableTint="?android:attr/textColorPrimary" android:text="@string/disable_carrier_button_text" - style="@style/Keyguard.TextView" - android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorPrimary" android:textSize="@dimen/kg_status_line_font_size" - android:textAllCaps="@bool/kg_use_all_caps" /> + android:visibility="gone" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml index f2fe520f340f..7db0fe908ec0 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml @@ -47,8 +47,7 @@ <include layout="@layout/keyguard_esim_area" android:id="@+id/keyguard_esim_area" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/eca_overlap" /> + android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/row0" android:layout_width="match_parent" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml index a21ec29267fe..422bd4c12e8e 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml @@ -51,8 +51,7 @@ <include layout="@layout/keyguard_esim_area" android:id="@+id/keyguard_esim_area" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/eca_overlap" /> + android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/row0" diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml index e80cfafdd71a..a1068c65bae2 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml @@ -31,8 +31,4 @@ <!-- Overload default clock widget parameters --> <dimen name="widget_big_font_size">100dp</dimen> <dimen name="widget_label_font_size">18sp</dimen> - - <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. - Should be 0 on devices with plenty of room (e.g. tablets) --> - <dimen name="eca_overlap">0dip</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index ac131ae1c99f..46f6ab2399d1 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -44,10 +44,6 @@ <dimen name="keyguard_eca_top_margin">18dp</dimen> <dimen name="keyguard_eca_bottom_margin">12dp</dimen> - <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. - Should be 0 on devices with plenty of room (e.g. tablets) --> - <dimen name="eca_overlap">-10dip</dimen> - <!-- Slice header --> <dimen name="widget_title_font_size">20dp</dimen> <dimen name="widget_title_line_height">24dp</dimen> diff --git a/packages/SystemUI/res/drawable/ic_no_sim.xml b/packages/SystemUI/res/drawable/ic_no_sim.xml new file mode 100644 index 000000000000..ccfb6ea642cc --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_no_sim.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M20,17.175 L18,15.175V4Q18,4 18,4Q18,4 18,4H10.85L8.85,6L7.4,4.6L10,2H18Q18.825,2 19.413,2.587Q20,3.175 20,4ZM20.5,23.3 L6,8.8V20Q6,20 6,20Q6,20 6,20H18Q18,20 18,20Q18,20 18,20V17.975L20,19.975V20Q20,20.825 19.413,21.413Q18.825,22 18,22H6Q5.175,22 4.588,21.413Q4,20.825 4,20V8L4.6,7.4L0.7,3.5L2.125,2.1L21.9,21.875ZM13.525,10.675Q13.525,10.675 13.525,10.675Q13.525,10.675 13.525,10.675ZM11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Q11.65,14.475 11.65,14.475Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index c972624c04b1..006b260434bc 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -40,7 +40,6 @@ android:layout_height="match_parent" android:orientation="horizontal" android:gravity="center_vertical" - android:layout_marginEnd="@dimen/dream_overlay_status_bar_extra_margin" app:layout_constraintEnd_toStartOf="@+id/dream_overlay_system_status" /> <LinearLayout @@ -48,14 +47,15 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" - android:layout_marginStart="@dimen/dream_overlay_status_bar_extra_margin" + android:paddingStart="@dimen/dream_overlay_status_bar_extra_margin" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent"> <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/dream_overlay_alarm_set" android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/ic_alarm" android:tint="@android:color/white" android:visibility="gone" @@ -65,7 +65,7 @@ android:id="@+id/dream_overlay_priority_mode" android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/ic_qs_dnd_on" android:tint="@android:color/white" android:visibility="gone" @@ -75,7 +75,7 @@ android:id="@+id/dream_overlay_wifi_status" android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/ic_signal_wifi_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> @@ -84,7 +84,7 @@ android:id="@+id/dream_overlay_mic_off" android:layout_width="@dimen/dream_overlay_grey_chip_width" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/dream_overlay_mic_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_mic_off" /> @@ -93,7 +93,7 @@ android:id="@+id/dream_overlay_camera_off" android:layout_width="@dimen/dream_overlay_grey_chip_width" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/dream_overlay_camera_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_camera_off" /> @@ -102,7 +102,7 @@ android:id="@+id/dream_overlay_camera_mic_off" android:layout_width="@dimen/dream_overlay_grey_chip_width" android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" android:src="@drawable/dream_overlay_mic_and_camera_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index a8027238a0bf..37549c927a90 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -611,6 +611,33 @@ 2 - Override the setting to never bypass keyguard --> <integer name="config_face_unlock_bypass_override">0</integer> + <!-- Messages that should NOT be shown to the user during face authentication on keyguard. + This includes both lockscreen and bouncer. This should be used to hide messages that may be + too chatty or messages that the user can't do much about. Entries are defined in + android.hardware.biometrics.face@1.0 types.hal. + + Although not visibly shown to the user, these acquired messages (sent per face auth frame) + are still counted towards the total frames to determine whether a deferred message + (see config_face_help_msgs_defer_until_timeout) meets the threshold % of frames to show on + face timeout. --> + <integer-array name="config_face_acquire_device_entry_ignorelist" translatable="false" > + </integer-array> + + <!-- Which face help messages to defer until face auth times out. If face auth is cancelled + or ends on another error, then the message is never surfaced. May also never surface + if it doesn't meet a threshold % of authentication frames specified by. + config_face_help_msgs_defer_until_timeout_threshold. --> + <integer-array name="config_face_help_msgs_defer_until_timeout"> + </integer-array> + + <!-- Percentage of face auth frames received required to show a deferred message at + FACE_ERROR_TIMEOUT. See config_face_help_msgs_defer_until_timeout for messages + that are deferred.--> + <item name="config_face_help_msgs_defer_until_timeout_threshold" + translatable="false" format="float" type="dimen"> + .75 + </item> + <!-- Which face help messages to surface when fingerprint is also enrolled. Message ids correspond with the acquired ids in BiometricFaceConstants --> <integer-array name="config_face_help_msgs_when_fingerprint_enrolled"> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e0264c30eeec..1b169e2e1a31 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1464,7 +1464,7 @@ <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen> <dimen name="dream_overlay_notification_indicator_size">6dp</dimen> <dimen name="dream_overlay_grey_chip_width">56dp</dimen> - <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen> + <dimen name="dream_overlay_status_bar_extra_margin">8dp</dimen> <!-- Dream overlay complications related dimensions --> <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen> diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt index cbab0a75061e..fafc7744f439 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt @@ -23,9 +23,11 @@ import platform.test.screenshot.PathConfig /** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */ class SystemUIGoldenImagePathManager( pathConfig: PathConfig, + override val assetsPathRelativeToRepo: String = "tests/screenshot/assets" ) : GoldenImagePathManager( appContext = InstrumentationRegistry.getInstrumentation().context, + assetsPathRelativeToRepo = assetsPathRelativeToRepo, deviceLocalPath = InstrumentationRegistry.getInstrumentation() .targetContext diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java new file mode 100644 index 000000000000..30c062b66da9 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shared.system; + +import android.graphics.HardwareRenderer; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.os.Trace; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import android.view.View; +import android.view.ViewRootImpl; + +import java.util.function.Consumer; + +/** + * Helper class to apply surface transactions in sync with RenderThread. + * + * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't + * currently reference that class from the shared lib as it is hidden. + */ +public class SyncRtSurfaceTransactionApplierCompat { + + public static final int FLAG_ALL = 0xffffffff; + public static final int FLAG_ALPHA = 1; + public static final int FLAG_MATRIX = 1 << 1; + public static final int FLAG_WINDOW_CROP = 1 << 2; + public static final int FLAG_LAYER = 1 << 3; + public static final int FLAG_CORNER_RADIUS = 1 << 4; + public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5; + public static final int FLAG_VISIBILITY = 1 << 6; + public static final int FLAG_RELATIVE_LAYER = 1 << 7; + public static final int FLAG_SHADOW_RADIUS = 1 << 8; + + private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0; + + private final SurfaceControl mBarrierSurfaceControl; + private final ViewRootImpl mTargetViewRootImpl; + private final Handler mApplyHandler; + + private int mSequenceNumber = 0; + private int mPendingSequenceNumber = 0; + private Runnable mAfterApplyCallback; + + /** + * @param targetView The view in the surface that acts as synchronization anchor. + */ + public SyncRtSurfaceTransactionApplierCompat(View targetView) { + mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; + mBarrierSurfaceControl = mTargetViewRootImpl != null + ? mTargetViewRootImpl.getSurfaceControl() : null; + + mApplyHandler = new Handler(new Callback() { + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) { + onApplyMessage(msg.arg1); + return true; + } + return false; + } + }); + } + + private void onApplyMessage(int seqNo) { + mSequenceNumber = seqNo; + if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) { + Runnable r = mAfterApplyCallback; + mAfterApplyCallback = null; + r.run(); + } + } + + /** + * Schedules applying surface parameters on the next frame. + * + * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into + * this method to avoid synchronization issues. + */ + public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) { + if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) { + return; + } + + mPendingSequenceNumber++; + final int toApplySeqNo = mPendingSequenceNumber; + mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() { + @Override + public void onFrameDraw(long frame) { + if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) { + Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) + .sendToTarget(); + return; + } + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame); + Transaction t = new Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = + params[i]; + surfaceParams.applyTo(t); + } + if (mTargetViewRootImpl != null) { + mTargetViewRootImpl.mergeWithNextTransaction(t, frame); + } else { + t.apply(); + } + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) + .sendToTarget(); + } + }); + + // Make sure a frame gets scheduled. + mTargetViewRootImpl.getView().invalidate(); + } + + /** + * Calls the runnable when any pending apply calls have completed + */ + public void addAfterApplyCallback(final Runnable afterApplyCallback) { + if (mSequenceNumber == mPendingSequenceNumber) { + afterApplyCallback.run(); + } else { + if (mAfterApplyCallback == null) { + mAfterApplyCallback = afterApplyCallback; + } else { + final Runnable oldCallback = mAfterApplyCallback; + mAfterApplyCallback = new Runnable() { + @Override + public void run() { + afterApplyCallback.run(); + oldCallback.run(); + } + }; + } + } + } + + public static void applyParams(TransactionCompat t, + SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) { + params.applyTo(t.mTransaction); + } + + /** + * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is + * attached if necessary. + */ + public static void create(final View targetView, + final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) { + if (targetView == null) { + // No target view, no applier + callback.accept(null); + } else if (targetView.getViewRootImpl() != null) { + // Already attached, we're good to go + callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); + } else { + // Haven't been attached before we can get the view root + targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + targetView.removeOnAttachStateChangeListener(this); + callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // Do nothing + } + }); + } + } + + public static class SurfaceParams { + public static class Builder { + final SurfaceControl surface; + int flags; + float alpha; + float cornerRadius; + int backgroundBlurRadius; + Matrix matrix; + Rect windowCrop; + int layer; + SurfaceControl relativeTo; + int relativeLayer; + boolean visible; + float shadowRadius; + + /** + * @param surface The surface to modify. + */ + public Builder(SurfaceControl surface) { + this.surface = surface; + } + + /** + * @param alpha The alpha value to apply to the surface. + * @return this Builder + */ + public Builder withAlpha(float alpha) { + this.alpha = alpha; + flags |= FLAG_ALPHA; + return this; + } + + /** + * @param matrix The matrix to apply to the surface. + * @return this Builder + */ + public Builder withMatrix(Matrix matrix) { + this.matrix = new Matrix(matrix); + flags |= FLAG_MATRIX; + return this; + } + + /** + * @param windowCrop The window crop to apply to the surface. + * @return this Builder + */ + public Builder withWindowCrop(Rect windowCrop) { + this.windowCrop = new Rect(windowCrop); + flags |= FLAG_WINDOW_CROP; + return this; + } + + /** + * @param layer The layer to assign the surface. + * @return this Builder + */ + public Builder withLayer(int layer) { + this.layer = layer; + flags |= FLAG_LAYER; + return this; + } + + /** + * @param relativeTo The surface that's set relative layer to. + * @param relativeLayer The relative layer. + * @return this Builder + */ + public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) { + this.relativeTo = relativeTo; + this.relativeLayer = relativeLayer; + flags |= FLAG_RELATIVE_LAYER; + return this; + } + + /** + * @param radius the Radius for rounded corners to apply to the surface. + * @return this Builder + */ + public Builder withCornerRadius(float radius) { + this.cornerRadius = radius; + flags |= FLAG_CORNER_RADIUS; + return this; + } + + /** + * @param radius the Radius for the shadows to apply to the surface. + * @return this Builder + */ + public Builder withShadowRadius(float radius) { + this.shadowRadius = radius; + flags |= FLAG_SHADOW_RADIUS; + return this; + } + + /** + * @param radius the Radius for blur to apply to the background surfaces. + * @return this Builder + */ + public Builder withBackgroundBlur(int radius) { + this.backgroundBlurRadius = radius; + flags |= FLAG_BACKGROUND_BLUR_RADIUS; + return this; + } + + /** + * @param visible The visibility to apply to the surface. + * @return this Builder + */ + public Builder withVisibility(boolean visible) { + this.visible = visible; + flags |= FLAG_VISIBILITY; + return this; + } + + /** + * @return a new SurfaceParams instance + */ + public SurfaceParams build() { + return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer, + relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible, + shadowRadius); + } + } + + private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix, + Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer, + float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) { + this.flags = flags; + this.surface = surface; + this.alpha = alpha; + this.matrix = matrix; + this.windowCrop = windowCrop; + this.layer = layer; + this.relativeTo = relativeTo; + this.relativeLayer = relativeLayer; + this.cornerRadius = cornerRadius; + this.backgroundBlurRadius = backgroundBlurRadius; + this.visible = visible; + this.shadowRadius = shadowRadius; + } + + private final int flags; + private final float[] mTmpValues = new float[9]; + + public final SurfaceControl surface; + public final float alpha; + public final float cornerRadius; + public final int backgroundBlurRadius; + public final Matrix matrix; + public final Rect windowCrop; + public final int layer; + public final SurfaceControl relativeTo; + public final int relativeLayer; + public final boolean visible; + public final float shadowRadius; + + public void applyTo(SurfaceControl.Transaction t) { + if ((flags & FLAG_MATRIX) != 0) { + t.setMatrix(surface, matrix, mTmpValues); + } + if ((flags & FLAG_WINDOW_CROP) != 0) { + t.setWindowCrop(surface, windowCrop); + } + if ((flags & FLAG_ALPHA) != 0) { + t.setAlpha(surface, alpha); + } + if ((flags & FLAG_LAYER) != 0) { + t.setLayer(surface, layer); + } + if ((flags & FLAG_CORNER_RADIUS) != 0) { + t.setCornerRadius(surface, cornerRadius); + } + if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) { + t.setBackgroundBlurRadius(surface, backgroundBlurRadius); + } + if ((flags & FLAG_VISIBILITY) != 0) { + if (visible) { + t.show(surface); + } else { + t.hide(surface); + } + } + if ((flags & FLAG_RELATIVE_LAYER) != 0) { + t.setRelativeLayer(surface, relativeTo, relativeLayer); + } + if ((flags & FLAG_SHADOW_RADIUS) != 0) { + t.setShadowRadius(surface, shadowRadius); + } + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java new file mode 100644 index 000000000000..43a882a5f6be --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shared.system; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +public class TransactionCompat { + + final Transaction mTransaction; + + final float[] mTmpValues = new float[9]; + + public TransactionCompat() { + mTransaction = new Transaction(); + } + + public void apply() { + mTransaction.apply(); + } + + public TransactionCompat show(SurfaceControl surfaceControl) { + mTransaction.show(surfaceControl); + return this; + } + + public TransactionCompat hide(SurfaceControl surfaceControl) { + mTransaction.hide(surfaceControl); + return this; + } + + public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) { + mTransaction.setPosition(surfaceControl, x, y); + return this; + } + + public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) { + mTransaction.setBufferSize(surfaceControl, w, h); + return this; + } + + public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) { + mTransaction.setLayer(surfaceControl, z); + return this; + } + + public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) { + mTransaction.setAlpha(surfaceControl, alpha); + return this; + } + + public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) { + mTransaction.setOpaque(surfaceControl, opaque); + return this; + } + + public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx, + float dtdy, float dsdy) { + mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy); + return this; + } + + public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) { + mTransaction.setMatrix(surfaceControl, matrix, mTmpValues); + return this; + } + + public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) { + mTransaction.setWindowCrop(surfaceControl, crop); + return this; + } + + public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) { + mTransaction.setCornerRadius(surfaceControl, radius); + return this; + } + + public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) { + mTransaction.setBackgroundBlurRadius(surfaceControl, radius); + return this; + } + + public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) { + mTransaction.setColor(surfaceControl, color); + return this; + } + + public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl, + SurfaceControl relativeTo, int z) { + t.setRelativeLayer(surfaceControl, relativeTo, z); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt new file mode 100644 index 000000000000..b4bfca1185f4 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt @@ -0,0 +1,54 @@ +/* + * 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.keyguard + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageView +import androidx.core.graphics.drawable.DrawableCompat +import com.android.systemui.R + +abstract class KeyguardSimInputView(context: Context, attrs: AttributeSet) : + KeyguardPinBasedInputView(context, attrs) { + private var simImageView: ImageView? = null + private var disableESimButton: KeyguardEsimArea? = null + + override fun onFinishInflate() { + simImageView = findViewById(R.id.keyguard_sim) + disableESimButton = findViewById(R.id.keyguard_esim_area) + super.onFinishInflate() + } + + /** Set UI state based on whether there is a locked eSim card */ + fun setESimLocked(isESimLocked: Boolean, subId: Int) { + disableESimButton?.setSubscriptionId(subId) + disableESimButton?.visibility = if (isESimLocked) VISIBLE else GONE + simImageView?.visibility = if (isESimLocked) GONE else VISIBLE + } + + override fun reloadColors() { + super.reloadColors() + val customAttrs = intArrayOf(android.R.attr.textColorSecondary) + val a = context.obtainStyledAttributes(customAttrs) + val imageColor = a.getColor(0, 0) + a.recycle() + simImageView?.let { + val wrappedDrawable = DrawableCompat.wrap(it.drawable) + DrawableCompat.setTint(wrappedDrawable, imageColor) + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index ae9d3dfec3b2..9d170150a709 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -18,21 +18,14 @@ package com.android.keyguard; import android.content.Context; import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; - -import androidx.core.graphics.drawable.DrawableCompat; import com.android.systemui.R; /** * Displays a PIN pad for unlocking. */ -public class KeyguardSimPinView extends KeyguardPinBasedInputView { - private ImageView mSimImageView; +public class KeyguardSimPinView extends KeyguardSimInputView { public static final String TAG = "KeyguardSimPinView"; public KeyguardSimPinView(Context context) { @@ -43,12 +36,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { super(context, attrs); } - public void setEsimLocked(boolean locked, int subscriptionId) { - KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); - esimButton.setSubscriptionId(subscriptionId); - esimButton.setVisibility(locked ? View.VISIBLE : View.GONE); - } - @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -68,7 +55,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { @Override protected void onFinishInflate() { - mSimImageView = findViewById(R.id.keyguard_sim); super.onFinishInflate(); if (mEcaView instanceof EmergencyCarrierArea) { @@ -86,17 +72,4 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock); } - - @Override - public void reloadColors() { - super.reloadColors(); - - int[] customAttrs = {android.R.attr.textColorSecondary}; - TypedArray a = getContext().obtainStyledAttributes(customAttrs); - int imageColor = a.getColor(0, 0); - a.recycle(); - Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable()); - DrawableCompat.setTint(wrappedDrawable, imageColor); - } } - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index ecd88e6fe90c..76f7d785071d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -105,7 +105,7 @@ public class KeyguardSimPinViewController showDefaultMessage(); } - mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); + mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index c0971bf8c16d..5f45fe31a779 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -19,13 +19,8 @@ package com.android.keyguard; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; -import android.widget.ImageView; - -import androidx.core.graphics.drawable.DrawableCompat; import com.android.systemui.R; @@ -33,8 +28,7 @@ import com.android.systemui.R; /** * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. */ -public class KeyguardSimPukView extends KeyguardPinBasedInputView { - private ImageView mSimImageView; +public class KeyguardSimPukView extends KeyguardSimInputView { private static final boolean DEBUG = KeyguardConstants.DEBUG; public static final String TAG = "KeyguardSimPukView"; @@ -86,7 +80,6 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { @Override protected void onFinishInflate() { - mSimImageView = findViewById(R.id.keyguard_sim); super.onFinishInflate(); if (mEcaView instanceof EmergencyCarrierArea) { @@ -104,18 +97,4 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { return getContext().getString( com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock); } - - @Override - public void reloadColors() { - super.reloadColors(); - - int[] customAttrs = {android.R.attr.textColorSecondary}; - TypedArray a = getContext().obtainStyledAttributes(customAttrs); - int imageColor = a.getColor(0, 0); - a.recycle(); - Drawable wrappedDrawable = DrawableCompat.wrap(mSimImageView.getDrawable()); - DrawableCompat.setTint(wrappedDrawable, imageColor); - } } - - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 203f9b660536..d8cffd7984ba 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -30,7 +30,6 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; -import android.view.View; import android.view.WindowManager; import android.widget.ImageView; @@ -173,11 +172,9 @@ public class KeyguardSimPukViewController if (mShowDefaultMessage) { showDefaultMessage(); } - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); - KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area); - esimButton.setSubscriptionId(mSubId); - esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); + mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId); + mPasswordEntry.requestFocus(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 763b29e52cf2..32c1cf9802ab 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -157,6 +157,7 @@ import com.google.android.collect.Lists; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -167,6 +168,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; @@ -281,6 +283,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; private final UiEventLogger mUiEventLogger; + private final Set<Integer> mFaceAcquiredInfoIgnoreList; private int mStatusBarState; private final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { @@ -1023,6 +1026,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFaceAuthFailed() { Assert.isMainThread(); + mLogger.d("onFaceAuthFailed"); mFaceCancelSignal = null; setFaceRunningState(BIOMETRIC_STATE_STOPPED); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1639,6 +1643,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) { + return; + } handleFaceHelp(helpMsgId, helpString.toString()); } @@ -1931,6 +1938,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mWakeOnFingerprintAcquiredStart = context.getResources() .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start); + mFaceAcquiredInfoIgnoreList = Arrays.stream( + mContext.getResources().getIntArray( + R.array.config_face_acquire_device_entry_ignorelist)) + .boxed() + .collect(Collectors.toSet()); mHandler = new Handler(mainLooper) { @Override diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt new file mode 100644 index 000000000000..2c2ab7b39161 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -0,0 +1,71 @@ +/* + * 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.keyguard.logging + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.dagger.BiometricMessagesLog +import javax.inject.Inject + +/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */ +@SysUISingleton +class FaceMessageDeferralLogger +@Inject +constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) : + BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger") + +open class BiometricMessageDeferralLogger( + private val logBuffer: LogBuffer, + private val tag: String +) { + fun reset() { + logBuffer.log(tag, DEBUG, "reset") + } + + fun logUpdateMessage(acquiredInfo: Int, helpString: String) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + str1 = helpString + }, + { "updateMessage acquiredInfo=$int1 helpString=$str1" } + ) + } + + fun logFrameProcessed( + acquiredInfo: Int, + totalFrames: Int, + mostFrequentAcquiredInfoToDefer: String? // may not meet the threshold + ) { + logBuffer.log( + tag, + DEBUG, + { + int1 = acquiredInfo + int2 = totalFrames + str1 = mostFrequentAcquiredInfoToDefer + }, + { + "frameProcessed acquiredInfo=$int1 totalFrames=$int2 " + + "messageToShowOnTimeout=$str1" + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt deleted file mode 100644 index f2d8aaa30f21..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -/** - * Provides whether an acquired error message should be shown immediately when its received (see - * [shouldDefer]) or should be shown when the biometric error is received [getDeferredMessage]. - * @property excludedMessages messages that are excluded from counts - * @property messagesToDefer messages that shouldn't show immediately when received, but may be - * shown later if the message is the most frequent message processed and meets [THRESHOLD] - * percentage of all messages (excluding [excludedMessages]) - */ -class BiometricMessageDeferral( - private val excludedMessages: Set<Int>, - private val messagesToDefer: Set<Int> -) { - private val msgCounts: MutableMap<Int, Int> = HashMap() // msgId => frequency of msg - private val msgIdToCharSequence: MutableMap<Int, CharSequence> = HashMap() // msgId => message - private var totalRelevantMessages = 0 - private var mostFrequentMsgIdToDefer: Int? = null - - /** Reset all saved counts. */ - fun reset() { - totalRelevantMessages = 0 - msgCounts.clear() - msgIdToCharSequence.clear() - } - - /** Whether the given message should be deferred instead of being shown immediately. */ - fun shouldDefer(acquiredMsgId: Int): Boolean { - return messagesToDefer.contains(acquiredMsgId) - } - - /** - * Adds the acquiredMsgId to the counts if it's not in [excludedMessages]. We still count - * messages that shouldn't be deferred in these counts. - */ - fun processMessage(acquiredMsgId: Int, helpString: CharSequence) { - if (excludedMessages.contains(acquiredMsgId)) { - return - } - - totalRelevantMessages++ - msgIdToCharSequence[acquiredMsgId] = helpString - - val newAcquiredMsgCount = msgCounts.getOrDefault(acquiredMsgId, 0) + 1 - msgCounts[acquiredMsgId] = newAcquiredMsgCount - if ( - messagesToDefer.contains(acquiredMsgId) && - (mostFrequentMsgIdToDefer == null || - newAcquiredMsgCount > msgCounts.getOrDefault(mostFrequentMsgIdToDefer!!, 0)) - ) { - mostFrequentMsgIdToDefer = acquiredMsgId - } - } - - /** - * Get the most frequent deferred message that meets the [THRESHOLD] percentage of processed - * messages excluding [excludedMessages]. - * @return null if no messages have been deferred OR deferred messages didn't meet the - * [THRESHOLD] percentage of messages to show. - */ - fun getDeferredMessage(): CharSequence? { - mostFrequentMsgIdToDefer?.let { - if (msgCounts.getOrDefault(it, 0) > (THRESHOLD * totalRelevantMessages)) { - return msgIdToCharSequence[mostFrequentMsgIdToDefer] - } - } - - return null - } - companion object { - const val THRESHOLD = .5f - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt new file mode 100644 index 000000000000..fabc1c1bb908 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt @@ -0,0 +1,141 @@ +/* + * 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.biometrics + +import android.content.res.Resources +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.keyguard.logging.FaceMessageDeferralLogger +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import java.io.PrintWriter +import java.util.* +import javax.inject.Inject + +/** + * Provides whether a face acquired help message should be shown immediately when its received or + * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage]. + */ +@SysUISingleton +class FaceHelpMessageDeferral +@Inject +constructor( + @Main resources: Resources, + logBuffer: FaceMessageDeferralLogger, + dumpManager: DumpManager +) : + BiometricMessageDeferral( + resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(), + resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold), + logBuffer, + dumpManager + ) + +/** + * @property messagesToDefer messages that shouldn't show immediately when received, but may be + * shown later if the message is the most frequent acquiredInfo processed and meets [threshold] + * percentage of all passed acquired frames. + */ +open class BiometricMessageDeferral( + private val messagesToDefer: Set<Int>, + private val threshold: Float, + private val logBuffer: BiometricMessageDeferralLogger, + dumpManager: DumpManager +) : Dumpable { + private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap() + private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap() + private var mostFrequentAcquiredInfoToDefer: Int? = null + private var totalFrames = 0 + + init { + dumpManager.registerDumpable(this.javaClass.name, this) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("messagesToDefer=$messagesToDefer") + pw.println("totalFrames=$totalFrames") + pw.println("threshold=$threshold") + } + + /** Reset all saved counts. */ + fun reset() { + totalFrames = 0 + mostFrequentAcquiredInfoToDefer = null + acquiredInfoToFrequency.clear() + acquiredInfoToHelpString.clear() + logBuffer.reset() + } + + /** Updates the message associated with the acquiredInfo if it's a message we may defer. */ + fun updateMessage(acquiredInfo: Int, helpString: String) { + if (!messagesToDefer.contains(acquiredInfo)) { + return + } + if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) { + logBuffer.logUpdateMessage(acquiredInfo, helpString) + acquiredInfoToHelpString[acquiredInfo] = helpString + } + } + + /** Whether the given message should be deferred instead of being shown immediately. */ + fun shouldDefer(acquiredMsgId: Int): Boolean { + return messagesToDefer.contains(acquiredMsgId) + } + + /** Adds the acquiredInfo frame to the counts. We account for all frames. */ + fun processFrame(acquiredInfo: Int) { + if (messagesToDefer.isEmpty()) { + return + } + + totalFrames++ + + val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1 + acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount + if ( + messagesToDefer.contains(acquiredInfo) && + (mostFrequentAcquiredInfoToDefer == null || + newAcquiredInfoCount > + acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0)) + ) { + mostFrequentAcquiredInfoToDefer = acquiredInfo + } + + logBuffer.logFrameProcessed( + acquiredInfo, + totalFrames, + mostFrequentAcquiredInfoToDefer?.toString() + ) + } + + /** + * Get the most frequent deferred message that meets the [threshold] percentage of processed + * frames. + * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the + * [threshold] percentage. + */ + fun getDeferredMessage(): CharSequence? { + mostFrequentAcquiredInfoToDefer?.let { + if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) { + return acquiredInfoToHelpString[it] + } + } + return null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index 823255c38a84..f244cb009ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -61,6 +61,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { private final Map<Integer, View> mStatusIcons = new HashMap<>(); private ViewGroup mSystemStatusViewGroup; + private ViewGroup mExtraSystemStatusViewGroup; public DreamOverlayStatusBarView(Context context) { this(context, null); @@ -98,7 +99,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON, fetchStatusIconForResId(R.id.dream_overlay_priority_mode)); - mSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items); + mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status); + mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items); } void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) { @@ -110,11 +112,12 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { icon.setContentDescription(contentDescription); } icon.setVisibility(show ? View.VISIBLE : View.GONE); + mSystemStatusViewGroup.setVisibility(areAnyStatusIconsVisible() ? View.VISIBLE : View.GONE); } void setExtraStatusBarItemViews(List<View> views) { - removeAllStatusBarItemViews(); - views.forEach(view -> mSystemStatusViewGroup.addView(view)); + removeAllExtraStatusBarItemViews(); + views.forEach(view -> mExtraSystemStatusViewGroup.addView(view)); } private View fetchStatusIconForResId(int resId) { @@ -122,7 +125,16 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { return Objects.requireNonNull(statusIcon); } - void removeAllStatusBarItemViews() { - mSystemStatusViewGroup.removeAllViews(); + void removeAllExtraStatusBarItemViews() { + mExtraSystemStatusViewGroup.removeAllViews(); + } + + private boolean areAnyStatusIconsVisible() { + for (int i = 0; i < mSystemStatusViewGroup.getChildCount(); i++) { + if (mSystemStatusViewGroup.getChildAt(i).getVisibility() == View.VISIBLE) { + return true; + } + } + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 6f505504b186..aa59cc666caa 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -192,7 +192,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mDreamOverlayNotificationCountProvider.ifPresent( provider -> provider.removeCallback(mNotificationCountCallback)); mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback); - mView.removeAllStatusBarItemViews(); + mView.removeAllExtraStatusBarItemViews(); mTouchInsetSession.clear(); mIsAttached = false; diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index e9c0337ea0ef..cd6c57aea76b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -187,6 +187,9 @@ public class Flags { // 802 - wallpaper rendering public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802); + // 803 - screen contents translation + public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803); + /***************************************/ // 900 - media public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index bd75ab2adb54..6f38f4f53b7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -241,9 +241,8 @@ class KeyguardUnlockAnimationController @Inject constructor( */ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null - private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null + private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null private var surfaceBehindRemoteAnimationStartTime: Long = 0 - private var surfaceBehindParams: SyncRtSurfaceTransactionApplier.SurfaceParams? = null /** * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the @@ -458,7 +457,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * (fingerprint, tap, etc.) and the keyguard is going away. */ fun notifyStartSurfaceBehindRemoteAnimation( - target: RemoteAnimationTarget, + targets: Array<RemoteAnimationTarget>, startTime: Long, requestedShowSurfaceBehindKeyguard: Boolean ) { @@ -467,10 +466,7 @@ class KeyguardUnlockAnimationController @Inject constructor( keyguardViewController.viewRootImpl.view) } - // New animation, new params. - surfaceBehindParams = null - - surfaceBehindRemoteAnimationTarget = target + surfaceBehindRemoteAnimationTargets = targets surfaceBehindRemoteAnimationStartTime = startTime // If we specifically requested that the surface behind be made visible (vs. it being made @@ -597,7 +593,7 @@ class KeyguardUnlockAnimationController @Inject constructor( * keyguard dismiss amount and the method of dismissal. */ private fun updateSurfaceBehindAppearAmount() { - if (surfaceBehindRemoteAnimationTarget == null) { + if (surfaceBehindRemoteAnimationTargets == null) { return } @@ -715,63 +711,60 @@ class KeyguardUnlockAnimationController @Inject constructor( * cancelled). */ fun setSurfaceBehindAppearAmount(amount: Float) { - if (surfaceBehindRemoteAnimationTarget == null) { - return - } + surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget -> + val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height() + + var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + + (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * + MathUtils.clamp(amount, 0f, 1f)) - // Otherwise, animate in the surface's scale/transltion. - val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height() - - var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR + - (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * - MathUtils.clamp(amount, 0f, 1f)) - - // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations, so - // don't also scale the window. - if (keyguardStateController.isDismissingFromSwipe && - willUnlockWithInWindowLauncherAnimations) { - scaleFactor = 1f - } - - // Scale up from a point at the center-bottom of the surface. - surfaceBehindMatrix.setScale( - scaleFactor, - scaleFactor, - surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f, - surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y - ) - - // Translate up from the bottom. - surfaceBehindMatrix.postTranslate( - 0f, - surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) - ) - - // If we're snapping the keyguard back, immediately begin fading it out. - val animationAlpha = - if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount - else surfaceBehindAlpha - - // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is unable - // to draw - val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget?.leash - if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE && - sc?.isValid == true) { - with(SurfaceControl.Transaction()) { - setMatrix(sc, surfaceBehindMatrix, tmpFloat) - setCornerRadius(sc, roundedCornerRadius) - setAlpha(sc, animationAlpha) - apply() + // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations, + // so don't also scale the window. + if (keyguardStateController.isDismissingFromSwipe && + willUnlockWithInWindowLauncherAnimations) { + scaleFactor = 1f } - } else { - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withMatrix(surfaceBehindMatrix) - .withCornerRadius(roundedCornerRadius) - .withAlpha(animationAlpha) - .build() + + // Translate up from the bottom. + surfaceBehindMatrix.setTranslate( + surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(), + surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount) ) + + // Scale up from a point at the center-bottom of the surface. + surfaceBehindMatrix.postScale( + scaleFactor, + scaleFactor, + keyguardViewController.viewRootImpl.width / 2f, + surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y + ) + + // If we're snapping the keyguard back, immediately begin fading it out. + val animationAlpha = + if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount + else surfaceBehindAlpha + + // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is + // unable to draw + val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash + if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE && + sc?.isValid == true) { + with(SurfaceControl.Transaction()) { + setMatrix(sc, surfaceBehindMatrix, tmpFloat) + setCornerRadius(sc, roundedCornerRadius) + setAlpha(sc, animationAlpha) + apply() + } + } else { + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget.leash) + .withMatrix(surfaceBehindMatrix) + .withCornerRadius(roundedCornerRadius) + .withAlpha(animationAlpha) + .build() + ) + } } } @@ -796,8 +789,7 @@ class KeyguardUnlockAnimationController @Inject constructor( launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) // That target is no longer valid since the animation finished, null it out. - surfaceBehindRemoteAnimationTarget = null - surfaceBehindParams = null + surfaceBehindRemoteAnimationTargets = null playingCannedUnlockAnimation = false willUnlockWithInWindowLauncherAnimations = false @@ -829,7 +821,6 @@ class KeyguardUnlockAnimationController @Inject constructor( private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) { surfaceTransactionApplier!!.scheduleApply(params) - surfaceBehindParams = params } private fun fadeInSurfaceBehind() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d4e0f061ba06..be1d162e2b08 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -831,7 +831,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {} @Override - public void onLaunchAnimationCancelled() { + public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) { Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded); } @@ -2587,18 +2587,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mInteractionJankMonitor.begin( createInteractionJankMonitorConf("DismissPanel")); - // Apply the opening animation on root task if exists - RemoteAnimationTarget aniTarget = apps[0]; - for (RemoteAnimationTarget tmpTarget : apps) { - if (tmpTarget.taskId != -1 && !tmpTarget.hasAnimatingParent) { - aniTarget = tmpTarget; - break; - } - } // Pass the surface and metadata to the unlock animation controller. mKeyguardUnlockAnimationControllerLazy.get() .notifyStartSurfaceBehindRemoteAnimation( - aniTarget, startTime, mSurfaceBehindRemoteAnimationRequested); + apps, startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RemoteAnimationDisabled")); diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java new file mode 100644 index 000000000000..7f1ad6d20c16 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.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.systemui.log.dagger; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral} + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface BiometricMessagesLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index c2a87649adef..756feb0c55c2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -287,6 +287,17 @@ public class LogModule { } /** + * Provides a {@link LogBuffer} for use by + * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}. + */ + @Provides + @SysUISingleton + @BiometricMessagesLog + public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) { + return factory.create("BiometricMessagesLog", 150); + } + + /** * Provides a {@link LogBuffer} for use by the status bar network controller. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java index 6fe06e085556..7d3e82c9d47f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java @@ -107,7 +107,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME, - getInteractionDeviceType(source)); + getInteractionDeviceType(source), + getLoggingPackageName()); } /** @@ -121,7 +122,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING, - SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE); + SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE, + getLoggingPackageName()); } /** @@ -135,7 +137,8 @@ public class MediaOutputMetricLogger { SysUiStatsLog.write( SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT, SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION, - getInteractionDeviceType(source)); + getInteractionDeviceType(source), + getLoggingPackageName()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt index aa10f7e2738f..b565f3c22f24 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt @@ -18,38 +18,57 @@ package com.android.systemui.media.taptotransfer.common import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel +import com.android.systemui.temporarydisplay.TemporaryViewLogger /** * A logger for media tap-to-transfer events. * - * @property deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver". + * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver". */ class MediaTttLogger( - private val deviceTypeTag: String, - private val buffer: LogBuffer -){ + deviceTypeTag: String, + buffer: LogBuffer +) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) { /** Logs a change in the chip state for the given [mediaRouteId]. */ - fun logStateChange(stateName: String, mediaRouteId: String) { + fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) { buffer.log( - BASE_TAG + deviceTypeTag, + tag, LogLevel.DEBUG, { str1 = stateName str2 = mediaRouteId + str3 = packageName }, - { "State changed to $str1 for ID=$str2" } + { "State changed to $str1 for ID=$str2 package=$str3" } ) } - /** Logs that we removed the chip for the given [reason]. */ - fun logChipRemoval(reason: String) { + /** Logs that we couldn't find information for [packageName]. */ + fun logPackageNotFound(packageName: String) { buffer.log( - BASE_TAG + deviceTypeTag, + tag, LogLevel.DEBUG, - { str1 = reason }, - { "Chip removed due to $str1" } + { str1 = packageName }, + { "Package $str1 could not be found" } ) } + + /** + * Logs that a removal request has been bypassed (ignored). + * + * @param removalReason the reason that the chip removal was requested. + * @param bypassReason the reason that the request was bypassed. + */ + fun logRemovalBypass(removalReason: String, bypassReason: String) { + buffer.log( + tag, + LogLevel.DEBUG, + { + str1 = removalReason + str2 = bypassReason + }, + { "Chip removal requested due to $str1; however, removal was ignored because $str2" }) + } } private const val BASE_TAG = "MediaTtt" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt new file mode 100644 index 000000000000..792ae7ca6049 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -0,0 +1,112 @@ +/* + * 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.taptotransfer.common + +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import com.android.internal.widget.CachingIconView +import com.android.settingslib.Utils +import com.android.systemui.R + +/** Utility methods for media tap-to-transfer. */ +class MediaTttUtils { + companion object { + // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and + // UpdateMediaTapToTransferReceiverDisplayTest + const val WINDOW_TITLE = "Media Transfer Chip View" + const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED" + + /** + * Returns the information needed to display the icon. + * + * The information will either contain app name and icon of the app playing media, or a + * default name and icon if we can't find the app name/icon. + * + * @param appPackageName the package name of the app playing the media. + * @param logger the logger to use for any errors. + */ + fun getIconInfoFromPackageName( + context: Context, + appPackageName: String?, + logger: MediaTttLogger + ): IconInfo { + if (appPackageName != null) { + try { + val contentDescription = + context.packageManager + .getApplicationInfo( + appPackageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + .loadLabel(context.packageManager) + .toString() + return IconInfo( + contentDescription, + drawable = context.packageManager.getApplicationIcon(appPackageName), + isAppIcon = true + ) + } catch (e: PackageManager.NameNotFoundException) { + logger.logPackageNotFound(appPackageName) + } + } + return IconInfo( + contentDescription = + context.getString(R.string.media_output_dialog_unknown_launch_app_name), + drawable = + context.resources.getDrawable(R.drawable.ic_cast).apply { + this.setTint( + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + ) + }, + isAppIcon = false + ) + } + + /** + * Sets an icon to be displayed by the given view. + * + * @param iconSize the size in pixels that the icon should be. If null, the size of + * [appIconView] will not be adjusted. + */ + fun setIcon( + appIconView: CachingIconView, + icon: Drawable, + iconContentDescription: CharSequence, + iconSize: Int? = null, + ) { + iconSize?.let { size -> + val lp = appIconView.layoutParams + lp.width = size + lp.height = size + appIconView.layoutParams = lp + } + + appIconView.contentDescription = iconContentDescription + appIconView.setImageDrawable(icon) + } + } +} + +data class IconInfo( + val contentDescription: String, + val drawable: Drawable, + /** + * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. + */ + val isAppIcon: Boolean +) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 5d6d683f93f6..dfd9e22c14b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -35,6 +35,7 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS @@ -61,7 +62,7 @@ class MediaTttChipControllerReceiver @Inject constructor( powerManager: PowerManager, @Main private val mainHandler: Handler, private val uiEventLogger: MediaTttReceiverUiEventLogger, -) : TemporaryViewDisplayController<ChipReceiverInfo>( +) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>( context, logger, windowManager, @@ -70,6 +71,8 @@ class MediaTttChipControllerReceiver @Inject constructor( configurationController, powerManager, R.layout.media_ttt_chip_receiver, + MediaTttUtils.WINDOW_TITLE, + MediaTttUtils.WAKE_REASON, ) { @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS override val windowLayoutParams = commonWindowLayoutParams.apply { @@ -107,7 +110,7 @@ class MediaTttChipControllerReceiver @Inject constructor( ) { val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState") @@ -137,13 +140,26 @@ class MediaTttChipControllerReceiver @Inject constructor( override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { super.updateView(newInfo, currentView) - val iconName = setIcon( - currentView, - newInfo.routeInfo.clientPackageName, - newInfo.appIconDrawableOverride, - newInfo.appNameOverride + + val iconInfo = MediaTttUtils.getIconInfoFromPackageName( + context, newInfo.routeInfo.clientPackageName, logger + ) + val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable + val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription + val iconSize = context.resources.getDimensionPixelSize( + if (iconInfo.isAppIcon) { + R.dimen.media_ttt_icon_size_receiver + } else { + R.dimen.media_ttt_generic_icon_size_receiver + } + ) + + MediaTttUtils.setIcon( + currentView.requireViewById(R.id.app_icon), + iconDrawable, + iconContentDescription, + iconSize, ) - currentView.contentDescription = iconName } override fun animateViewIn(view: ViewGroup) { @@ -161,15 +177,6 @@ class MediaTttChipControllerReceiver @Inject constructor( startRipple(view.requireViewById(R.id.ripple)) } - override fun getIconSize(isAppIcon: Boolean): Int? = - context.resources.getDimensionPixelSize( - if (isAppIcon) { - R.dimen.media_ttt_icon_size_receiver - } else { - R.dimen.media_ttt_generic_icon_size_receiver - } - ) - /** Returns the amount that the chip will be translated by in its intro animation. */ private fun getTranslationAmount(): Int { return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index bde588c14fc8..4379d25406bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -34,8 +34,7 @@ import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS * @property stateInt the integer from [StatusBarManager] corresponding with this state. * @property stringResId the res ID of the string that should be displayed in the chip. Null if the * state should not have the chip be displayed. - * @property isMidTransfer true if the state represents that a transfer is currently ongoing. - * @property isTransferFailure true if the state represents that the transfer has failed. + * @property transferStatus the transfer status that the chip state represents. * @property timeout the amount of time this chip should display on the screen before it times out * and disappears. */ @@ -43,8 +42,7 @@ enum class ChipStateSender( @StatusBarManager.MediaTransferSenderState val stateInt: Int, val uiEvent: UiEventLogger.UiEventEnum, @StringRes val stringResId: Int?, - val isMidTransfer: Boolean = false, - val isTransferFailure: Boolean = false, + val transferStatus: TransferStatus, val timeout: Long = DEFAULT_TIMEOUT_MILLIS ) { /** @@ -56,6 +54,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST, R.string.media_move_closer_to_start_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -68,6 +67,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST, R.string.media_move_closer_to_end_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -78,7 +78,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED, R.string.media_transfer_playing_different_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -90,7 +90,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED, R.string.media_transfer_playing_this_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -100,7 +100,8 @@ enum class ChipStateSender( TRANSFER_TO_RECEIVER_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED, - R.string.media_transfer_playing_different_device + R.string.media_transfer_playing_different_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -135,7 +136,8 @@ enum class ChipStateSender( TRANSFER_TO_THIS_DEVICE_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, - R.string.media_transfer_playing_this_device + R.string.media_transfer_playing_this_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -169,7 +171,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that a transfer back to this device has failed. */ @@ -177,14 +179,15 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that this device is far away from any receiver device. */ FAR_FROM_RECEIVER( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER, - stringResId = null + stringResId = null, + transferStatus = TransferStatus.TOO_FAR, ); /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 0c1ebd70c572..e539f3fd842d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -34,6 +34,7 @@ import com.android.systemui.animation.ViewHierarchyAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason @@ -57,7 +58,7 @@ class MediaTttChipControllerSender @Inject constructor( configurationController: ConfigurationController, powerManager: PowerManager, private val uiEventLogger: MediaTttSenderUiEventLogger -) : TemporaryViewDisplayController<ChipSenderInfo>( +) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>( context, logger, windowManager, @@ -66,6 +67,8 @@ class MediaTttChipControllerSender @Inject constructor( configurationController, powerManager, R.layout.media_ttt_chip, + MediaTttUtils.WINDOW_TITLE, + MediaTttUtils.WAKE_REASON, ) { override val windowLayoutParams = commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) @@ -94,7 +97,7 @@ class MediaTttChipControllerSender @Inject constructor( ) { val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") @@ -103,7 +106,7 @@ class MediaTttChipControllerSender @Inject constructor( uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { - removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER::class.simpleName!!) + removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name) } else { displayView(ChipSenderInfo(chipState, routeInfo, undoCallback)) } @@ -118,7 +121,14 @@ class MediaTttChipControllerSender @Inject constructor( val chipState = newInfo.state // App icon - val iconName = setIcon(currentView, newInfo.routeInfo.clientPackageName) + val iconInfo = MediaTttUtils.getIconInfoFromPackageName( + context, newInfo.routeInfo.clientPackageName, logger + ) + MediaTttUtils.setIcon( + currentView.requireViewById(R.id.app_icon), + iconInfo.drawable, + iconInfo.contentDescription + ) // Text val otherDeviceName = newInfo.routeInfo.name.toString() @@ -127,7 +137,7 @@ class MediaTttChipControllerSender @Inject constructor( // Loading currentView.requireViewById<View>(R.id.loading).visibility = - chipState.isMidTransfer.visibleIfTrue() + (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue() // Undo val undoView = currentView.requireViewById<View>(R.id.undo) @@ -139,12 +149,12 @@ class MediaTttChipControllerSender @Inject constructor( // Failure currentView.requireViewById<View>(R.id.failure_icon).visibility = - chipState.isTransferFailure.visibleIfTrue() + (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue() // For accessibility currentView.requireViewById<ViewGroup>( R.id.media_ttt_sender_chip_inner - ).contentDescription = "$iconName $chipText" + ).contentDescription = "${iconInfo.contentDescription} $chipText" } override fun animateViewIn(view: ViewGroup) { @@ -162,10 +172,17 @@ class MediaTttChipControllerSender @Inject constructor( } override fun removeView(removalReason: String) { - // Don't remove the chip if we're mid-transfer since the user should still be able to - // see the status of the transfer. (But do remove it if it's finally timed out.) - if (info?.state?.isMidTransfer == true && - removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT) { + // Don't remove the chip if we're in progress or succeeded, since the user should still be + // able to see the status of the transfer. (But do remove it if it's finally timed out.) + val transferStatus = info?.state?.transferStatus + if ( + (transferStatus == TransferStatus.IN_PROGRESS || + transferStatus == TransferStatus.SUCCEEDED) && + removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT + ) { + logger.logRemovalBypass( + removalReason, bypassReason = "transferStatus=${transferStatus.name}" + ) return } super.removeView(removalReason) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt new file mode 100644 index 000000000000..f15720df5245 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt @@ -0,0 +1,31 @@ +/* + * 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.taptotransfer.sender + +/** Represents the different possible transfer states that we could be in. */ +enum class TransferStatus { + /** The transfer hasn't started yet. */ + NOT_STARTED, + /** The transfer is currently ongoing but hasn't completed yet. */ + IN_PROGRESS, + /** The transfer has completed successfully. */ + SUCCEEDED, + /** The transfer has completed with a failure. */ + FAILED, + /** The device is too far away to do a transfer. */ + TOO_FAR, +} 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 58426656e4bf..e9a6c25c0e6d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -50,6 +50,7 @@ public class QSIconViewImpl extends QSIconView { protected int mIconSizePx; private boolean mAnimationEnabled = true; private int mState = -1; + private boolean mDisabledByPolicy = false; private int mTint; @Nullable private QSTile.Icon mLastIcon; @@ -159,14 +160,10 @@ public class QSIconViewImpl extends QSIconView { } protected void setIcon(ImageView iv, QSTile.State state, boolean allowAnimations) { - if (state.disabledByPolicy) { - iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color)); - } else { - iv.clearColorFilter(); - } - if (state.state != mState) { + if (state.state != mState || state.disabledByPolicy != mDisabledByPolicy) { int color = getColor(state); mState = state.state; + mDisabledByPolicy = state.disabledByPolicy; if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); } else { @@ -241,8 +238,8 @@ public class QSIconViewImpl extends QSIconView { */ private static int getIconColorForState(Context context, QSTile.State state) { if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) { - return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA, - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)); + return Utils.getColorAttrDefaultColor( + context, com.android.internal.R.attr.textColorTertiary); } else if (state.state == Tile.STATE_INACTIVE) { return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); } else if (state.state == Tile.STATE_ACTIVE) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 163ee2af600c..972b24343d10 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -100,14 +100,15 @@ open class QSTileViewImpl @JvmOverloads constructor( Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) - private val colorLabelUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorLabelInactive) + private val colorLabelUnavailable = + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) private val colorSecondaryLabelActive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse) private val colorSecondaryLabelInactive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary) private val colorSecondaryLabelUnavailable = - Utils.applyAlpha(UNAVAILABLE_ALPHA, colorSecondaryLabelInactive) + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) private lateinit var label: TextView protected lateinit var secondaryLabel: TextView diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 408c61f8f387..e06c97756ea7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -19,9 +19,6 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_FIRST_FRAME_RECEIVED; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_START; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.view.View.GONE; @@ -53,6 +50,7 @@ import android.content.pm.UserInfo; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; +import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; @@ -82,7 +80,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; -import com.android.systemui.biometrics.BiometricMessageDeferral; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -184,6 +182,7 @@ public class KeyguardIndicationController { private long mChargingTimeRemaining; private String mBiometricErrorMessageToShowOnScreenOn; private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow; + private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -233,7 +232,8 @@ public class KeyguardIndicationController { LockPatternUtils lockPatternUtils, ScreenLifecycle screenLifecycle, KeyguardBypassController keyguardBypassController, - AccessibilityManager accessibilityManager) { + AccessibilityManager accessibilityManager, + FaceHelpMessageDeferral faceHelpMessageDeferral) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mDevicePolicyManager = devicePolicyManager; @@ -254,6 +254,7 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mScreenLifecycle.addObserver(mScreenObserver); + mFaceAcquiredMessageDeferral = faceHelpMessageDeferral; mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>(); int[] msgIds = context.getResources().getIntArray( com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled); @@ -1041,8 +1042,22 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.processFrame(acquireInfo); + } + } + + @Override public void onBiometricHelp(int msgId, String helpString, BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.updateMessage(msgId, helpString); + if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { + return; + } + } + // TODO(b/141025588): refactor to reduce repetition of code/comments // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to @@ -1053,17 +1068,6 @@ public class KeyguardIndicationController { return; } - if (biometricSourceType == FACE) { - if (msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED) { - mFaceAcquiredMessageDeferral.reset(); - } else { - mFaceAcquiredMessageDeferral.processMessage(msgId, helpString); - if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) { - return; - } - } - } - final boolean faceAuthSoftError = biometricSourceType == FACE && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; final boolean faceAuthFailed = biometricSourceType == FACE @@ -1109,11 +1113,23 @@ public class KeyguardIndicationController { } @Override + public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + mFaceAcquiredMessageDeferral.reset(); + } + } + + @Override public void onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType) { CharSequence deferredFaceMessage = null; if (biometricSourceType == FACE) { - deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) { + deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); + if (DEBUG) { + Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage); + } + } mFaceAcquiredMessageDeferral.reset(); } @@ -1308,14 +1324,4 @@ public class KeyguardIndicationController { } } }; - - private final BiometricMessageDeferral mFaceAcquiredMessageDeferral = - new BiometricMessageDeferral( - Set.of( - FACE_ACQUIRED_GOOD, - FACE_ACQUIRED_START, - FACE_ACQUIRED_FIRST_FRAME_RECEIVED - ), - Set.of(FACE_ACQUIRED_TOO_DARK) - ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index 31b21c9b5321..553826dda919 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -136,7 +136,7 @@ class NotificationLaunchAnimatorController( headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate) } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started // here? notificationShadeWindowViewController.setExpandAnimationRunning(false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 820f6e4ff718..0b63bbfec877 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1737,13 +1737,18 @@ public class CentralSurfacesImpl extends CoreStartable implements } @Override - public void onLaunchAnimationCancelled() { + public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) { + if (newKeyguardOccludedState != null) { + mKeyguardViewMediator.setOccluded( + newKeyguardOccludedState, false /* animate */); + } + // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the // animation so that we can assume that mIsLaunchingActivityOverLockscreen // being true means that we will collapse the shade (or at least run the // post collapse runnables) later on. CentralSurfacesImpl.this.mIsLaunchingActivityOverLockscreen = false; - getDelegate().onLaunchAnimationCancelled(); + getDelegate().onLaunchAnimationCancelled(newKeyguardOccludedState); } }; } else if (dismissShade) { @@ -3623,6 +3628,7 @@ public class CentralSurfacesImpl extends CoreStartable implements dismissVolumeDialog(); mWakeUpCoordinator.setFullyAwake(false); mKeyguardBypassController.onStartedGoingToSleep(); + mStatusBarTouchableRegionManager.updateTouchableRegion(); // The unlocked screen off and fold to aod animations might use our LightRevealScrim - // we need to be expanded for it to be visible. @@ -3651,6 +3657,7 @@ public class CentralSurfacesImpl extends CoreStartable implements // once we fully woke up. updateRevealEffect(true /* wakingUp */); updateNotificationPanelTouchState(); + mStatusBarTouchableRegionManager.updateTouchableRegion(); // If we are waking up during the screen off animation, we should undo making the // expanded visible (we did that so the LightRevealScrim would be visible). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index c0922163903f..ee948c04df38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -51,7 +51,7 @@ class StatusBarLaunchAnimatorController( centralSurfaces.notificationPanelViewController.applyLaunchAnimationProgress(linearProgress) } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { delegate.onLaunchAnimationCancelled() centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(false) centralSurfaces.onLaunchAnimationCancelled(isLaunchForActivity) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 75dac1a093dd..d9c0293fe13d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -55,6 +55,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private final Context mContext; private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationShadeWindowController mNotificationShadeWindowController; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mIsStatusBarExpanded = false; private boolean mShouldAdjustInsets = false; @@ -72,7 +73,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable { Context context, NotificationShadeWindowController notificationShadeWindowController, ConfigurationController configurationController, - HeadsUpManagerPhone headsUpManager + HeadsUpManagerPhone headsUpManager, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController ) { mContext = context; initResources(); @@ -115,6 +117,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable { mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> { updateTouchableRegion(); }); + + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; } protected void setup( @@ -179,7 +183,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { /** * Set the touchable portion of the status bar based on what elements are visible. */ - private void updateTouchableRegion() { + public void updateTouchableRegion() { boolean hasCutoutInset = (mNotificationShadeWindowView != null) && (mNotificationShadeWindowView.getRootWindowInsets() != null) && (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null); @@ -242,12 +246,25 @@ public final class StatusBarTouchableRegionManager implements Dumpable { touchableRegion.union(bounds); } + /** + * Helper to let us know when calculating the region is not needed because we know the entire + * screen needs to be touchable. + */ + private boolean shouldMakeEntireScreenTouchable() { + // The touchable region is always the full area when expanded, whether we're showing the + // shade or the bouncer. It's also fully touchable when the screen off animation is playing + // since we don't want stray touches to go through the light reveal scrim to whatever is + // underneath. + return mIsStatusBarExpanded + || mCentralSurfaces.isBouncerShowing() + || mUnlockedScreenOffAnimationController.isAnimationPlaying(); + } + private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = new OnComputeInternalInsetsListener() { @Override public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsStatusBarExpanded || mCentralSurfaces.isBouncerShowing()) { - // The touchable region is always the full area when expanded + if (shouldMakeEntireScreenTouchable()) { return; } 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 62bda2c76e9f..1d5b88e7dee0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -1122,11 +1122,19 @@ public class UserSwitcherController implements Dumpable { } public String getName(Context context, UserRecord item) { + return getName(context, item, false); + } + + /** + * Returns the name for the given {@link UserRecord}. + */ + public String getName(Context context, UserRecord item, boolean isTablet) { return LegacyUserUiHelper.getUserRecordName( context, item, mController.isGuestUserAutoCreated(), - mController.isGuestUserResetting()); + mController.isGuestUserResetting(), + isTablet); } protected static ColorFilter getDisabledUserAvatarColorFilter() { @@ -1136,8 +1144,12 @@ public class UserSwitcherController implements Dumpable { } protected static Drawable getIconDrawable(Context context, UserRecord item) { + return getIconDrawable(context, item, false); + } + protected static Drawable getIconDrawable(Context context, UserRecord item, + boolean isTablet) { int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - item.isAddUser, item.isGuest, item.isAddSupervisedUser); + item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet); return context.getDrawable(iconRes); } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 734eeecec215..a52e2aff52c1 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -19,12 +19,10 @@ package com.android.systemui.temporarydisplay import android.annotation.LayoutRes import android.annotation.SuppressLint import android.content.Context -import android.content.pm.PackageManager import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.os.PowerManager import android.os.SystemClock -import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import android.view.WindowManager @@ -33,11 +31,7 @@ import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT import androidx.annotation.CallSuper -import com.android.internal.widget.CachingIconView -import com.android.settingslib.Utils -import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor @@ -50,17 +44,22 @@ import com.android.systemui.util.concurrency.DelayableExecutor * The generic type T is expected to contain all the information necessary for the subclasses to * display the view in a certain state, since they receive <T> in [updateView]. * - * TODO(b/245610654): Remove all the media-specific logic from this class. + * @property windowTitle the title to use for the window that displays the temporary view. Should be + * normally cased, like "Window Title". + * @property wakeReason a string used for logging if we needed to wake the screen in order to + * display the temporary view. Should be screaming snake cased, like WAKE_REASON. */ -abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( +abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>( internal val context: Context, - internal val logger: MediaTttLogger, + internal val logger: U, internal val windowManager: WindowManager, @Main private val mainExecutor: DelayableExecutor, private val accessibilityManager: AccessibilityManager, private val configurationController: ConfigurationController, private val powerManager: PowerManager, @LayoutRes private val viewLayoutRes: Int, + private val windowTitle: String, + private val wakeReason: String, ) { /** * Window layout params that will be used as a starting point for the [windowLayoutParams] of @@ -72,7 +71,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( height = WindowManager.LayoutParams.WRAP_CONTENT type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - title = WINDOW_TITLE + title = windowTitle format = PixelFormat.TRANSLUCENT setTrustedOverlay() } @@ -115,10 +114,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( powerManager.wakeUp( SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION, - "com.android.systemui:media_tap_to_transfer_activated" + "com.android.systemui:$wakeReason", ) } - + logger.logChipAddition() inflateAndUpdateView(newInfo) } @@ -192,80 +191,8 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>( * appears. */ open fun animateViewIn(view: ViewGroup) {} - - /** - * Returns the size that the icon should be, or null if no size override is needed. - */ - open fun getIconSize(isAppIcon: Boolean): Int? = null - - /** - * An internal method to set the icon on the view. - * - * This is in the common superclass since both the sender and the receiver show an icon. - * - * @param appPackageName the package name of the app playing the media. Will be used to fetch - * the app icon and app name if overrides aren't provided. - * - * @return the content description of the icon. - */ - internal fun setIcon( - currentView: ViewGroup, - appPackageName: String?, - appIconDrawableOverride: Drawable? = null, - appNameOverride: CharSequence? = null, - ): CharSequence { - val appIconView = currentView.requireViewById<CachingIconView>(R.id.app_icon) - val iconInfo = getIconInfo(appPackageName) - - getIconSize(iconInfo.isAppIcon)?.let { size -> - val lp = appIconView.layoutParams - lp.width = size - lp.height = size - appIconView.layoutParams = lp - } - - appIconView.contentDescription = appNameOverride ?: iconInfo.iconName - appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon) - return appIconView.contentDescription - } - - /** - * Returns the information needed to display the icon. - * - * The information will either contain app name and icon of the app playing media, or a default - * name and icon if we can't find the app name/icon. - */ - private fun getIconInfo(appPackageName: String?): IconInfo { - if (appPackageName != null) { - try { - return IconInfo( - iconName = context.packageManager.getApplicationInfo( - appPackageName, PackageManager.ApplicationInfoFlags.of(0) - ).loadLabel(context.packageManager).toString(), - icon = context.packageManager.getApplicationIcon(appPackageName), - isAppIcon = true - ) - } catch (e: PackageManager.NameNotFoundException) { - Log.w(TAG, "Cannot find package $appPackageName", e) - } - } - return IconInfo( - iconName = context.getString(R.string.media_output_dialog_unknown_launch_app_name), - icon = context.resources.getDrawable(R.drawable.ic_cast).apply { - this.setTint( - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) - ) - }, - isAppIcon = false - ) - } } -// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and -// UpdateMediaTapToTransferReceiverDisplayTest -private const val WINDOW_TITLE = "Media Transfer Chip View" -private val TAG = TemporaryViewDisplayController::class.simpleName!! - object TemporaryDisplayRemovalReason { const val REASON_TIMEOUT = "TIMEOUT" const val REASON_SCREEN_TAP = "SCREEN_TAP" diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt new file mode 100644 index 000000000000..606a11a84686 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -0,0 +1,36 @@ +/* + * 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.temporarydisplay + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel + +/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */ +open class TemporaryViewLogger( + internal val buffer: LogBuffer, + internal val tag: String, +) { + /** Logs that we added the chip to a new window. */ + fun logChipAddition() { + buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" }) + } + + /** Logs that we removed the chip for the given [reason]. */ + fun logChipRemoval(reason: String) { + buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index d43f739f9e71..5e2dde6be046 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -173,8 +173,8 @@ open class UserSwitcherActivity @Inject constructor( this, R.layout.user_switcher_fullscreen_popup_item, layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) }, - { item: UserRecord -> adapter.findUserIcon(item).mutate().apply { + { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, + { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply { setTint(resources.getColor( R.color.user_switcher_fullscreen_popup_item_tint, getTheme() @@ -322,6 +322,9 @@ open class UserSwitcherActivity @Inject constructor( return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) } + /** + * Provides views to populate the option menu. + */ private class ItemAdapter( val parentContext: Context, val resource: Int, @@ -375,20 +378,20 @@ open class UserSwitcherActivity @Inject constructor( return view } - override fun getName(context: Context, item: UserRecord): String { + override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { return if (item == manageUserRecord) { getString(R.string.manage_users) } else { - super.getName(context, item) + super.getName(context, item, isTablet) } } - fun findUserIcon(item: UserRecord): Drawable { + fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable { if (item == manageUserRecord) { return getDrawable(R.drawable.ic_manage_users) } if (item.info == null) { - return getIconDrawable(this@UserSwitcherActivity, item) + return getIconDrawable(this@UserSwitcherActivity, item, isTablet) } val userIcon = userManager.getUserIcon(item.info.id) if (userIcon != null) { diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt index 18369d9a71d2..15fdc352d864 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt @@ -49,8 +49,11 @@ object LegacyUserUiHelper { isAddUser: Boolean, isGuest: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { - return if (isAddUser) { + return if (isAddUser && isTablet) { + R.drawable.ic_account_circle_filled + } else if (isAddUser) { R.drawable.ic_add } else if (isGuest) { R.drawable.ic_account_circle @@ -67,6 +70,7 @@ object LegacyUserUiHelper { record: UserRecord, isGuestUserAutoCreated: Boolean, isGuestUserResetting: Boolean, + isTablet: Boolean = false, ): String { val resourceId: Int? = getGuestUserRecordNameResourceId(record) return when { @@ -80,6 +84,7 @@ object LegacyUserUiHelper { isGuestUserResetting = isGuestUserResetting, isAddUser = record.isAddUser, isAddSupervisedUser = record.isAddSupervisedUser, + isTablet = isTablet, ) ) } @@ -108,12 +113,14 @@ object LegacyUserUiHelper { isGuestUserResetting: Boolean, isAddUser: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { check(isGuest || isAddUser || isAddSupervisedUser) return when { isGuest && isGuestUserAutoCreated && isGuestUserResetting -> com.android.settingslib.R.string.guest_resetting + isGuest && isTablet -> com.android.settingslib.R.string.guest_new_guest isGuest && isGuestUserAutoCreated -> com.android.internal.R.string.guest_name isGuest -> com.android.internal.R.string.guest_name isAddUser -> com.android.settingslib.R.string.user_add_user diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index e2790e47fe06..a61cd23b60fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -161,7 +161,18 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() - verify(controller).onLaunchAnimationCancelled() + verify(controller).onLaunchAnimationCancelled(false /* newKeyguardOccludedState */) + verify(controller, never()).onLaunchAnimationStart(anyBoolean()) + } + + @Test + fun passesOccludedStateToLaunchAnimationCancelled_ifTrue() { + val runner = activityLaunchAnimator.createRunner(controller) + runner.onAnimationCancelled(true /* isKeyguardOccluded */) + runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) + + waitForIdleSync() + verify(controller).onLaunchAnimationCancelled(true /* newKeyguardOccludedState */) verify(controller, never()).onLaunchAnimationStart(anyBoolean()) } @@ -253,7 +264,7 @@ private class TestLaunchAnimatorController(override var launchContainer: ViewGro assertOnMainThread() } - override fun onLaunchAnimationCancelled() { + override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { assertOnMainThread() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt deleted file mode 100644 index 419fedf99c15..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class BiometricMessageDeferralTest : SysuiTestCase() { - - @Test - fun testProcessNoMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf()) - - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testProcessNonDeferredMessages_noDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there are no deferred messages processed - for (i in 0..3) { - biometricMessageDeferral.processMessage(4, "test") - } - - // THEN getDeferredMessage is null - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testAllProcessedMessagesWereDeferred() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN all the processed messages are a deferred message - for (i in 0..3) { - biometricMessageDeferral.processMessage(1, "test") - } - - // THEN deferredMessage will return the string associated with the deferred msgId - assertEquals("test", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testReturnsMostFrequentDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // WHEN there's two msgId=1 processed and one msgId=2 processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN the most frequent deferred message is that meets the threshold is returned - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_mustMeetThreshold() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1)) - - // WHEN more nonDeferredMessages are shown than the deferred message - val totalMessages = 10 - val nonDeferredMessagesCount = - (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until nonDeferredMessagesCount) { - biometricMessageDeferral.processMessage(4, "non-deferred-msg") - } - for (i in nonDeferredMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there's no deferred message because it didn't meet the threshold - assertNull(biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testDeferredMessage_manyExcludedMessages_getDeferredMessage() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1)) - - // WHEN more excludedMessages are shown than the deferred message - val totalMessages = 10 - val excludedMessagesCount = (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1 - for (i in 0 until excludedMessagesCount) { - biometricMessageDeferral.processMessage(3, "excluded-msg") - } - for (i in excludedMessagesCount until totalMessages) { - biometricMessageDeferral.processMessage(1, "msgId-1") - } - - // THEN there IS a deferred message because the deferred msg meets the threshold amongst the - // non-excluded messages - assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testResetClearsOutCounts() { - val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2)) - - // GIVEN two msgId=1 events processed - biometricMessageDeferral.processMessage(1, "msgId-1") - biometricMessageDeferral.processMessage(1, "msgId-1") - - // WHEN counts are reset and then a single deferred message is processed (msgId=2) - biometricMessageDeferral.reset() - biometricMessageDeferral.processMessage(2, "msgId-2") - - // THEN msgId-2 is the deferred message since the two msgId=1 events were reset - assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) - } - - @Test - fun testShouldDefer() { - // GIVEN should defer msgIds 1 and 2 - val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1, 2)) - - // THEN shouldDefer returns true for ids 1 & 2 - assertTrue(biometricMessageDeferral.shouldDefer(1)) - assertTrue(biometricMessageDeferral.shouldDefer(2)) - - // THEN should defer returns false for ids 3 & 4 - assertFalse(biometricMessageDeferral.shouldDefer(3)) - assertFalse(biometricMessageDeferral.shouldDefer(4)) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt new file mode 100644 index 000000000000..c9ccdb36da89 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt @@ -0,0 +1,188 @@ +/* + * 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.biometrics + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricMessageDeferralLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FaceHelpMessageDeferralTest : SysuiTestCase() { + val threshold = .75f + @Mock lateinit var logger: BiometricMessageDeferralLogger + @Mock lateinit var dumpManager: DumpManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testProcessFrame_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.processFrame(1) + verify(logger).logFrameProcessed(1, 1, "1") + } + + @Test + fun testUpdateMessage_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.updateMessage(1, "hi") + verify(logger).logUpdateMessage(1, "hi") + } + + @Test + fun testReset_logs() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + biometricMessageDeferral.reset() + verify(logger).reset() + } + + @Test + fun testProcessNoMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(emptySet()) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessNonDeferredMessages_noDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there are no deferred messages processed + for (i in 0..3) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "test") + } + + // THEN getDeferredMessage is null + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testProcessMessagesWithDeferredMessage_deferredMessageWasNeverGivenAString() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + biometricMessageDeferral.processFrame(1) + + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testAllProcessedMessagesWereDeferred() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN all the processed messages are a deferred message + for (i in 0..3) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "test") + } + + // THEN deferredMessage will return the string associated with the deferred msgId + assertEquals("test", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testReturnsMostFrequentDeferredMessage() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2 + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN the most frequent deferred message is that meets the threshold is returned + assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testDeferredMessage_mustMeetThreshold() { + val biometricMessageDeferral = createMsgDeferral(setOf(1)) + + // WHEN more nonDeferredMessages are shown than the deferred message + val totalMessages = 10 + val nonDeferredMessagesCount = (totalMessages * threshold).toInt() + 1 + for (i in 0 until nonDeferredMessagesCount) { + biometricMessageDeferral.processFrame(4) + biometricMessageDeferral.updateMessage(4, "non-deferred-msg") + } + for (i in nonDeferredMessagesCount until totalMessages) { + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + } + + // THEN there's no deferred message because it didn't meet the threshold + assertNull(biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testResetClearsOutCounts() { + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // GIVEN two msgId=1 events processed + biometricMessageDeferral.processFrame( + 1, + ) + biometricMessageDeferral.updateMessage(1, "msgId-1") + biometricMessageDeferral.processFrame(1) + biometricMessageDeferral.updateMessage(1, "msgId-1") + + // WHEN counts are reset and then a single deferred message is processed (msgId=2) + biometricMessageDeferral.reset() + biometricMessageDeferral.processFrame(2) + biometricMessageDeferral.updateMessage(2, "msgId-2") + + // THEN msgId-2 is the deferred message since the two msgId=1 events were reset + assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage()) + } + + @Test + fun testShouldDefer() { + // GIVEN should defer msgIds 1 and 2 + val biometricMessageDeferral = createMsgDeferral(setOf(1, 2)) + + // THEN shouldDefer returns true for ids 1 & 2 + assertTrue(biometricMessageDeferral.shouldDefer(1)) + assertTrue(biometricMessageDeferral.shouldDefer(2)) + + // THEN should defer returns false for ids 3 & 4 + assertFalse(biometricMessageDeferral.shouldDefer(3)) + assertFalse(biometricMessageDeferral.shouldDefer(4)) + } + + private fun createMsgDeferral(messagesToDefer: Set<Int>): BiometricMessageDeferral { + return BiometricMessageDeferral(messagesToDefer, threshold, logger, dumpManager) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 7f6b79b48939..4ebae98d1246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -310,7 +310,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { @Test public void testOnViewDetachedRemovesViews() { mController.onViewDetached(); - verify(mView).removeAllStatusBarItemViews(); + verify(mView).removeAllExtraStatusBarItemViews(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index eecbee56d203..a78c902a1f30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -27,11 +27,11 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor.forClass import org.mockito.Mock +import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -60,7 +60,18 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub - private lateinit var remoteAnimationTarget: RemoteAnimationTarget + private var surfaceControl1 = mock(SurfaceControl::class.java) + private var remoteTarget1 = RemoteAnimationTarget( + 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), + mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(), + mock(ActivityManager.RunningTaskInfo::class.java), false) + + private var surfaceControl2 = mock(SurfaceControl::class.java) + private var remoteTarget2 = RemoteAnimationTarget( + 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), + mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(), + mock(ActivityManager.RunningTaskInfo::class.java), false) + private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget> @Before fun setUp() { @@ -77,10 +88,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { // All of these fields are final, so we can't mock them, but are needed so that the surface // appear amount setter doesn't short circuit. - remoteAnimationTarget = RemoteAnimationTarget( - 0, 0, null, false, Rect(), Rect(), 0, Point(), Rect(), Rect(), - mock(WindowConfiguration::class.java), false, mock(SurfaceControl::class.java), Rect(), - mock(ActivityManager.RunningTaskInfo::class.java), false) + remoteAnimationTargets = arrayOf(remoteTarget1) // Set the surface applier to our mock so that we can verify the arguments passed to it. // This applier does not have any side effects within the unlock animation controller, so @@ -99,7 +107,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -130,7 +138,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -154,7 +162,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -176,7 +184,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -196,7 +204,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { @Test fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -210,7 +218,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { `when`(notificationShadeWindowController.isLaunchingActivity).thenReturn(true) keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -225,11 +233,46 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( - remoteAnimationTarget, + remoteAnimationTargets, 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) } + + /** + * If we are not wake and unlocking, we expect the unlock animation to play normally. + */ + @Test + fun surfaceAnimation_multipleTargets() { + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + arrayOf(remoteTarget1, remoteTarget2), + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + // Set appear to 50%, we'll just verify that we're not applying the identity matrix which + // means an animation is in progress. + keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f) + + val captor = forClass(SyncRtSurfaceTransactionApplier.SurfaceParams::class.java) + verify(surfaceTransactionApplier, times(2)).scheduleApply(captor.capture()) + + val allParams = captor.allValues + + val remainingTargets = mutableListOf(surfaceControl1, surfaceControl2) + allParams.forEach { params -> + assertTrue(!params.matrix.isIdentity) + remainingTargets.remove(params.surface) + } + + // Make sure we called applyParams with each of the surface controls once. The order does + // not matter, so don't explicitly check for that. + assertTrue(remainingTargets.isEmpty()) + + // Since the animation is running, we should not have finished the remote animation. + verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished( + false /* cancelled */) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt index d95e5c48256c..1078cdaa57c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt @@ -23,11 +23,11 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.LogcatEchoTracker import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock -import java.io.PrintWriter -import java.io.StringWriter @SmallTest class MediaTttLoggerTest : SysuiTestCase() { @@ -43,32 +43,46 @@ class MediaTttLoggerTest : SysuiTestCase() { } @Test - fun logStateChange_bufferHasDeviceTypeTagAndStateNameAndId() { + fun logStateChange_bufferHasDeviceTypeTagAndParamInfo() { val stateName = "test state name" val id = "test id" + val packageName = "this.is.a.package" - logger.logStateChange(stateName, id) - - val stringWriter = StringWriter() - buffer.dump(PrintWriter(stringWriter), tailLength = 0) - val actualString = stringWriter.toString() + logger.logStateChange(stateName, id, packageName) + val actualString = getStringFromBuffer() assertThat(actualString).contains(DEVICE_TYPE_TAG) assertThat(actualString).contains(stateName) assertThat(actualString).contains(id) + assertThat(actualString).contains(packageName) } @Test - fun logChipRemoval_bufferHasDeviceTypeAndReason() { - val reason = "test reason" - logger.logChipRemoval(reason) + fun logPackageNotFound_bufferHasPackageName() { + val packageName = "this.is.a.package" + logger.logPackageNotFound(packageName) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(packageName) + } + + @Test + fun logRemovalBypass_bufferHasReasons() { + val removalReason = "fakeRemovalReason" + val bypassReason = "fakeBypassReason" + + logger.logRemovalBypass(removalReason, bypassReason) + + val actualString = getStringFromBuffer() + assertThat(actualString).contains(removalReason) + assertThat(actualString).contains(bypassReason) + } + + private fun getStringFromBuffer(): String { val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) - val actualString = stringWriter.toString() - - assertThat(actualString).contains(DEVICE_TYPE_TAG) - assertThat(actualString).contains(reason) + return stringWriter.toString() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt new file mode 100644 index 000000000000..37f6434ea069 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -0,0 +1,138 @@ +/* + * 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.taptotransfer.common + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.internal.widget.CachingIconView +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +class MediaTttUtilsTest : SysuiTestCase() { + + private lateinit var appIconFromPackageName: Drawable + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var applicationInfo: ApplicationInfo + @Mock private lateinit var logger: MediaTttLogger + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // Set up our package manager to give valid information for [PACKAGE_NAME] only + appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!! + whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName) + whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) + whenever( + packageManager.getApplicationInfo(any(), any<PackageManager.ApplicationInfoFlags>()) + ) + .thenThrow(PackageManager.NameNotFoundException()) + whenever( + packageManager.getApplicationInfo( + Mockito.eq(PACKAGE_NAME), + any<PackageManager.ApplicationInfoFlags>() + ) + ) + .thenReturn(applicationInfo) + context.setMockPackageManager(packageManager) + } + + @Test + fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { + val iconInfo = + MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) + + assertThat(iconInfo.isAppIcon).isFalse() + assertThat(iconInfo.contentDescription) + .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + } + + @Test + fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() { + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger) + + assertThat(iconInfo.isAppIcon).isFalse() + assertThat(iconInfo.contentDescription) + .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + } + + @Test + fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() { + val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger) + + assertThat(iconInfo.isAppIcon).isTrue() + assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName) + assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME) + } + + @Test + fun setIcon_viewHasIconAndContentDescription() { + val view = CachingIconView(context) + val icon = context.getDrawable(R.drawable.ic_celebration)!! + val contentDescription = "Happy birthday!" + + MediaTttUtils.setIcon(view, icon, contentDescription) + + assertThat(view.drawable).isEqualTo(icon) + assertThat(view.contentDescription).isEqualTo(contentDescription) + } + + @Test + fun setIcon_iconSizeNull_viewSizeDoesNotChange() { + val view = CachingIconView(context) + val size = 456 + view.layoutParams = FrameLayout.LayoutParams(size, size) + + MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc") + + assertThat(view.layoutParams.width).isEqualTo(size) + assertThat(view.layoutParams.height).isEqualTo(size) + } + + @Test + fun setIcon_iconSizeProvided_viewSizeUpdates() { + val view = CachingIconView(context) + val size = 456 + view.layoutParams = FrameLayout.LayoutParams(size, size) + + val newSize = 40 + MediaTttUtils.setIcon( + view, + context.getDrawable(R.drawable.ic_cake)!!, + "desc", + iconSize = newSize + ) + + assertThat(view.layoutParams.width).isEqualTo(newSize) + assertThat(view.layoutParams.height).isEqualTo(newSize) + } +} + +private const val PACKAGE_NAME = "com.android.systemui" +private const val APP_NAME = "Fake App Name" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index e7b4593b0ebb..d41ad48676b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -173,37 +173,72 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test - fun setIcon_isAppIcon_usesAppIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_noOverrides_usesInfoFromAppIcon() { + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) + } + + @Test + fun updateView_appIconOverride_usesOverride() { + val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!! + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(drawableOverride) + } + + @Test + fun updateView_appNameOverride_usesOverride() { + val appNameOverride = "Sweet New App" + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride) + ) + + val view = getChipView() + assertThat(view.getAppIconView().contentDescription).isEqualTo(appNameOverride) + } + + @Test + fun updateView_isAppIcon_usesAppIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, PACKAGE_NAME) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = true) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @Test - fun setIcon_notAppIcon_usesGenericIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_notAppIcon_usesGenericIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = null)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, appPackageName = null) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = false) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @@ -226,8 +261,13 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { return viewCaptor.value as ViewGroup } - private fun getChipReceiverInfo(): ChipReceiverInfo = - ChipReceiverInfo(routeInfo, null, null) + private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo { + val routeInfo = MediaRoute2Info.Builder("id", "Test route name") + .addFeature("feature") + .setClientPackageName(packageName) + .build() + return ChipReceiverInfo(routeInfo, null, null) + } private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 52b6eed9a14d..ff0faf98fe1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -299,7 +299,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test @@ -587,15 +587,29 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeExecutor.runAllReady() verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) } @Test - fun transferToReceiverTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToReceiverTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) - controllerSender.removeView("fakeRemovalReason") + fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverTriggered()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverTriggered()) + + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) @@ -610,20 +624,106 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeExecutor.runAllReady() verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) } @Test - fun transferToThisDeviceTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToThisDeviceTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) + fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceTriggered()) + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceTriggered()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) } + @Test + fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + verify(logger).logRemovalBypass(any(), any()) + } + private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) private fun ViewGroup.getChipText(): String = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index ac8874b0e131..945cf7f8774f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -19,8 +19,10 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; @@ -86,6 +88,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; @@ -167,6 +170,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; @Mock + private FaceHelpMessageDeferral mFaceHelpMessageDeferral; + @Mock private ScreenLifecycle mScreenLifecycle; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @@ -259,7 +264,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils, - mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager); + mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager, + mFaceHelpMessageDeferral); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); @@ -535,7 +541,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.setVisible(true); mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); reset(mRotateTextViewController); @@ -582,7 +588,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN there's a face not recognized message mController.getKeyguardCallback().onBiometricHelp( - KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); @@ -748,8 +754,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(false); - // WHEN help message received + // WHEN help message received and deferred message is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -777,8 +785,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 0)).thenReturn(true); - // WHEN help message received + // WHEN help message received and deferredMessage is valid final String helpString = "helpMsg"; + when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString); + when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true); mKeyguardUpdateMonitorCallback.onBiometricHelp( BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK, helpString, @@ -1123,7 +1133,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen); } - @Test public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() { // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled @@ -1269,6 +1278,87 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen); } + @Test + public void faceOnAcquired_processFrame() { + createController(); + + // WHEN face sends an acquired message + final int acquireInfo = 1; + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo); + + // THEN face help message deferral should process the acquired frame + verify(mFaceHelpMessageDeferral).processFrame(acquireInfo); + } + + @Test + public void fingerprintOnAcquired_noProcessFrame() { + createController(); + + // WHEN fingerprint sends an acquired message + mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1); + + // THEN face help message deferral should NOT process any acquired frames + verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt()); + } + + @Test + public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() { + createController(); + + // WHEN fingerprint sends an onBiometricHelp + mKeyguardUpdateMonitorCallback.onBiometricHelp( + 1, + "placeholder", + BiometricSourceType.FINGERPRINT); + + // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString()); + verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt()); + } + + @Test + public void onBiometricFailed_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricError_resetFaceHelpMessageDeferral() { + createController(); + + // WHEN face has an error + mKeyguardUpdateMonitorCallback.onBiometricError(4, "string", + BiometricSourceType.FACE); + + // THEN face help message deferral is reset + verify(mFaceHelpMessageDeferral).reset(); + } + + @Test + public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() { + createController(); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + final int msgId = 1; + final String helpString = "test"; + mKeyguardUpdateMonitorCallback.onBiometricHelp( + msgId, + "test", + BiometricSourceType.FACE); + + // THEN face help message deferral is NOT reset and message IS updated + verify(mFaceHelpMessageDeferral, never()).reset(); + verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString); + } + + + private void sendUpdateDisclosureBroadcast() { mBroadcastReceiver.onReceive(mContext, new Intent()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index e616c26377d2..921b7efc38eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -17,20 +17,14 @@ package com.android.systemui.temporarydisplay import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable import android.os.PowerManager -import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager -import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.android.systemui.util.concurrency.DelayableExecutor @@ -42,9 +36,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test -import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -58,13 +50,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { private lateinit var fakeClock: FakeSystemClock private lateinit var fakeExecutor: FakeExecutor - private lateinit var appIconFromPackageName: Drawable @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var applicationInfo: ApplicationInfo - @Mock - private lateinit var logger: MediaTttLogger + private lateinit var logger: TemporaryViewLogger @Mock private lateinit var accessibilityManager: AccessibilityManager @Mock @@ -78,17 +65,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!! - whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName) - whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) - whenever(packageManager.getApplicationInfo( - any(), any<PackageManager.ApplicationInfoFlags>() - )).thenThrow(PackageManager.NameNotFoundException()) - whenever(packageManager.getApplicationInfo( - eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() - )).thenReturn(applicationInfo) - context.setMockPackageManager(packageManager) - whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())) .thenReturn(TIMEOUT_MS.toInt()) @@ -229,117 +205,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) } - @Test - fun setIcon_nullAppIconDrawableAndNullPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appIconDrawableOverride = null) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawableAndInvalidPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appIconDrawableOverride = null - ) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawable_iconIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, appIconDrawableOverride = null, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(appIconFromPackageName) - } - - @Test - fun setIcon_hasAppIconDrawable_iconIsDrawable() { - underTest.displayView(getState()) - val view = getView() - - val drawable = context.getDrawable(R.drawable.ic_alarm)!! - underTest.setIcon(view, PACKAGE_NAME, drawable, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(drawable) - } - - @Test - fun setIcon_nullAppNameAndNullPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppNameAndInvalidPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appNameOverride = null - ) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppName_iconContentDescriptionIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) - } - - @Test - fun setIcon_hasAppName_iconContentDescriptionIsAppNameOverride() { - underTest.displayView(getState()) - val view = getView() - - val appName = "Override App Name" - underTest.setIcon(view, PACKAGE_NAME, null, appName) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(appName) - } - - @Test - fun setIcon_iconSizeMatchesGetIconSize() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME) - view.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) - ) - - assertThat(view.getAppIconView().measuredWidth).isEqualTo(ICON_SIZE) - assertThat(view.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE) - } - private fun getState(name: String = "name") = ViewInfo(name) - private fun getView(): ViewGroup { - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(windowManager).addView(viewCaptor.capture(), any()) - return viewCaptor.value as ViewGroup - } - - private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) - private fun getConfigurationListener(): ConfigurationListener { val callbackCaptor = argumentCaptor<ConfigurationListener>() verify(configurationController).addCallback(capture(callbackCaptor)) @@ -348,13 +215,13 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { inner class TestController( context: Context, - logger: MediaTttLogger, + logger: TemporaryViewLogger, windowManager: WindowManager, @Main mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, configurationController: ConfigurationController, powerManager: PowerManager, - ) : TemporaryViewDisplayController<ViewInfo>( + ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>( context, logger, windowManager, @@ -363,6 +230,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { configurationController, powerManager, R.layout.media_ttt_chip, + "Window Title", + "WAKE_REASON", ) { var mostRecentViewInfo: ViewInfo? = null @@ -371,7 +240,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { super.updateView(newInfo, currentView) mostRecentViewInfo = newInfo } - override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE } inner class ViewInfo(val name: String) : TemporaryViewInfo { @@ -379,7 +247,4 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } } -private const val PACKAGE_NAME = "com.android.systemui" -private const val APP_NAME = "Fake App Name" private const val TIMEOUT_MS = 10000L -private const val ICON_SIZE = 47 diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt new file mode 100644 index 000000000000..c9f2b4db81ef --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt @@ -0,0 +1,70 @@ +/* + * 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.temporarydisplay + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.log.LogcatEchoTracker +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito + +@SmallTest +class TemporaryViewLoggerTest : SysuiTestCase() { + private lateinit var buffer: LogBuffer + private lateinit var logger: TemporaryViewLogger + + @Before + fun setUp() { + buffer = + LogBufferFactory(DumpManager(), Mockito.mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + logger = TemporaryViewLogger(buffer, TAG) + } + + @Test + fun logChipAddition_bufferHasLog() { + logger.logChipAddition() + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains(TAG) + } + + @Test + fun logChipRemoval_bufferHasTagAndReason() { + val reason = "test reason" + logger.logChipRemoval(reason) + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains(TAG) + assertThat(actualString).contains(reason) + } +} + +private const val TAG = "TestTag" diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index e5eed9928411..3ff879661bda 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -675,6 +675,11 @@ public class LocationManagerService extends ILocationManager.Stub implements return mInjector.getSettingsHelper().getIgnoreSettingsAllowlist(); } + @Override + public PackageTagsList getAdasAllowlist() { + return mInjector.getSettingsHelper().getAdasAllowlist(); + } + @Nullable @Override public ICancellationSignal getCurrentLocation(String provider, LocationRequest request, diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java index 98bae3dc247b..811e96ce6d82 100644 --- a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java +++ b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java @@ -96,6 +96,7 @@ public class LogAccessDialogActivity extends Activity implements // show Alert mAlert = mAlertDialog.create(); + mAlert.getWindow().setHideOverlayWindows(true); mAlert.show(); // set Alert Timeout diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index c3b479219853..0915c21dda59 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -657,6 +657,18 @@ final class DeletePackageHelper { final String packageName = versionedPackage.getPackageName(); final long versionCode = versionedPackage.getLongVersionCode(); + if (mPm.mProtectedPackages.isPackageDataProtected(userId, packageName)) { + mPm.mHandler.post(() -> { + try { + Slog.w(TAG, "Attempted to delete protected package: " + packageName); + observer.onPackageDeleted(packageName, + PackageManager.DELETE_FAILED_INTERNAL_ERROR, null); + } catch (RemoteException re) { + } + }); + return; + } + try { if (mPm.mInjector.getLocalService(ActivityTaskManagerInternal.class) .isBaseOfLockedTask(packageName)) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 036a292c3174..d13cfdb5927b 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1595,12 +1595,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { boolean pauseImmediately = false; boolean shouldAutoPip = false; if (resuming != null) { + // We do not want to trigger auto-PiP upon launch of a translucent activity. + final boolean resumingOccludesParent = resuming.occludesParent(); // Resuming the new resume activity only if the previous activity can't go into Pip // since we want to give Pip activities a chance to enter Pip before resuming the // next activity. final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( "shouldAutoPipWhilePausing", userLeaving); - if (userLeaving && lastResumedCanPip + if (userLeaving && resumingOccludesParent && lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) { shouldAutoPip = true; } else if (!lastResumedCanPip) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 6650f43c15ae..488fe676d265 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -46,8 +46,9 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -1873,8 +1874,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_WILL_IME_SHOWN; } } + Task parentTask = null; final ActivityRecord record = wc.asActivityRecord(); if (record != null) { + parentTask = record.getTask(); if (record.mUseTransferredAnimation) { flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; } @@ -1882,6 +1885,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_IS_VOICE_INTERACTION; } } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment != null && task == null) { + parentTask = taskFragment.getTask(); + } + if (parentTask != null) { + if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { + // Whether this is in a Task with embedded activity. + flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + } + final Rect taskBounds = parentTask.getBounds(); + final Rect startBounds = mAbsoluteBounds; + final Rect endBounds = wc.getBounds(); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + // Whether the container fills the Task bounds before and after the transition. + flags |= FLAG_FILLS_TASK; + } + } final DisplayContent dc = wc.asDisplayContent(); if (dc != null) { flags |= FLAG_IS_DISPLAY; @@ -1898,9 +1921,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (occludesKeyguard(wc)) { flags |= FLAG_OCCLUDES_KEYGUARD; } - if (wc.isEmbedded()) { - flags |= FLAG_IS_EMBEDDED; - } return flags; } } 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 8f186e40e201..28bcc03fb3e7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; @@ -31,7 +32,8 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -1062,12 +1064,14 @@ public class TransitionTests extends WindowTestsBase { } @Test - public void testIsEmbeddedChange() { + public void testFlagInTaskWithEmbeddedActivity() { final Transition transition = createTestTransition(TRANSIT_OPEN); final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; final ArraySet<WindowContainer> participants = transition.mParticipants; final Task task = createTask(mDisplayContent); + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + assertFalse(nonEmbeddedActivity.isEmbedded()); final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); mAtm.mTaskFragmentOrganizerController.registerOrganizer( ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); @@ -1082,20 +1086,72 @@ public class TransitionTests extends WindowTestsBase { changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */, + false /* exChg */)); // End states. closingActivity.mVisibleRequested = false; openingActivity.mVisibleRequested = true; + nonEmbeddedActivity.mVisibleRequested = false; participants.add(closingActivity); participants.add(openingActivity); + participants.add(nonEmbeddedActivity); final ArrayList<WindowContainer> targets = Transition.calculateTargets( participants, changes); final TransitionInfo info = Transition.calculateTransitionInfo( transition.mType, 0 /* flags */, targets, changes, mMockT); + // All windows in the Task should have FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY because the Task + // contains embedded activity. + assertEquals(3, info.getChanges().size()); + assertTrue(info.getChanges().get(0).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + assertTrue(info.getChanges().get(1).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + assertTrue(info.getChanges().get(2).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); + } + + @Test + public void testFlagFillsTask() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + final ArraySet<WindowContainer> participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + // Set to multi-windowing mode in order to set bounds. + task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + final Rect taskBounds = new Rect(0, 0, 500, 1000); + task.setBounds(taskBounds); + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + mAtm.mTaskFragmentOrganizerController.registerOrganizer( + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); + final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(1) + .setOrganizer(organizer) + .build(); + final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity(); + // Start states. + changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */, + false /* exChg */)); + changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + // End states. + nonEmbeddedActivity.mVisibleRequested = false; + embeddedActivity.mVisibleRequested = true; + embeddedTf.setBounds(new Rect(0, 0, 500, 500)); + + participants.add(nonEmbeddedActivity); + participants.add(embeddedTf); + final ArrayList<WindowContainer> targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + // The embedded with bounds overridden should not have the flag. assertEquals(2, info.getChanges().size()); - assertTrue((info.getChanges().get(0).getFlags() & FLAG_IS_EMBEDDED) != 0); - assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0); + assertFalse(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK)); + assertEquals(embeddedTf.getBounds(), info.getChanges().get(0).getEndAbsBounds()); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_FILLS_TASK)); } @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt new file mode 100644 index 000000000000..d11ca4950d16 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt @@ -0,0 +1,108 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class GameAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = + LauncherStrategyFactory.getInstance(instr).launcherStrategy, +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + + /** + * Swipes down in the mock game app. + * + * @return true if the swipe operation is successful. + */ + fun swipeDown(): Boolean { + val gameView = uiDevice.wait( + Until.findObject(By.res(getPackage(), GAME_APP_VIEW_RES)), WAIT_TIME_MS) + require(gameView != null) { "Mock game app view not found." } + + val bound = gameView.getVisibleBounds() + return uiDevice.swipe( + bound.centerX(), bound.top, bound.centerX(), bound.centerY(), SWIPE_STEPS) + } + + /** + * Switches to a recent app by quick switch gesture. This function can be used in both portrait + * and landscape mode. + * + * @param wmHelper Helper used to get window region. + * @param direction UiAutomator Direction enum to indicate the swipe direction. + * + * @return true if the swipe operation is successful. + */ + fun switchToPreviousAppByQuickSwitchGesture( + wmHelper: WindowManagerStateHelper, + direction: Direction + ): Boolean { + val ratioForScreenBottom = 0.97 + val fullView = wmHelper.getWindowRegion(component) + require(!fullView.isEmpty) { "Target $component view not found." } + + val bound = fullView.bounds + val targetYPos = bound.bottom * ratioForScreenBottom + val endX = when (direction) { + Direction.LEFT -> bound.left + Direction.RIGHT -> bound.right + else -> { + throw IllegalStateException("Only left or right direction is allowed.") + } + } + return uiDevice.swipe( + bound.centerX(), targetYPos.toInt(), endX, targetYPos.toInt(), SWIPE_STEPS) + } + + /** + * Waits for view idel with timeout, then checkes the target object whether visible on screen. + * + * @param packageName The targe application's package name. + * @param identifier The resource id of the target object. + * @param timeout The timeout duration in milliseconds. + * + * @return true if the target object exists. + */ + @JvmOverloads + fun isTargetObjVisible( + packageName: String, + identifier: String, + timeout: Long = WAIT_TIME_MS + ): Boolean { + uiDevice.waitForIdle(timeout) + return uiDevice.hasObject(By.res(packageName, identifier)) + } + + companion object { + private const val GAME_APP_VIEW_RES = "container" + private const val WAIT_TIME_MS = 3_000L + private const val SWIPE_STEPS = 30 + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt new file mode 100644 index 000000000000..3385784de8af --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt @@ -0,0 +1,70 @@ +/* + * 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 androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +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 MailAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + + fun openMail(rowIdx: Int) { + val rowSel = By.res(getPackage(), "mail_row_item_text") + .textEndsWith(String.format("%04d", rowIdx)) + var row: UiObject2? = null + for (i in 1..1000) { + row = uiDevice.wait(Until.findObject(rowSel), SHORT_WAIT_TIME_MS) + if (row != null) break + scrollDown() + } + require(row != null) {""} + row.click() + uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT) + } + + fun scrollDown() { + val listView = waitForMailList() + listView.scroll(Direction.DOWN, 1.0f) + } + + fun waitForMailList(): UiObject2 { + var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true) + val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT) + require(ret != null) {""} + return ret + } + + companion object { + private const val SHORT_WAIT_TIME_MS = 5000L + private const val MAIL_LIST_RES_ID = "mail_recycle_view" + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 66ad6f1263e4..b8ef1954d5fc 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -185,5 +185,34 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <service + android:name=".AssistantInteractionSessionService" + android:exported="true" + android:permission="android.permission.BIND_VOICE_INTERACTION" /> + <service + android:name=".AssistantRecognitionService" + android:exported="true" + android:label="Test Voice Interaction Service"> + <intent-filter> + <action android:name="android.speech.RecognitionService" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <meta-data + android:name="android.speech" + android:resource="@xml/recognition_service" /> + </service> + <service + android:name=".AssistantInteractionService" + android:exported="true" + android:label="Test Voice Interaction Service" + android:permission="android.permission.BIND_VOICE_INTERACTION"> + <intent-filter> + <action android:name="android.service.voice.VoiceInteractionService" /> + </intent-filter> + <meta-data + android:name="android.voice_interaction" + android:resource="@xml/interaction_service" /> + </service> </application> + <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml new file mode 100644 index 000000000000..eb7f3074ebfb --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <FrameLayout + android:id="@+id/vis_frame" + android:layout_width="match_parent" + android:layout_height="300dp" + android:layout_gravity="bottom" + android:background="#37474F"/> +</FrameLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml new file mode 100644 index 000000000000..2e661fbd3122 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml @@ -0,0 +1,20 @@ +<!-- + ~ 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. + --> + +<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android" + android:recognitionService="com.android.server.wm.flicker.testapp.AssistantRecognitionService" + android:sessionService="com.android.server.wm.flicker.testapp.AssistantInteractionSessionService" + android:supportsAssist="true" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml new file mode 100644 index 000000000000..2e124982084f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml @@ -0,0 +1,17 @@ +<!-- + ~ 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. + --> + +<recognition-service xmlns:android="http://schemas.android.com/apk/res/android" /> 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 42a37df191dc..45a47303990c 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 @@ -88,6 +88,10 @@ public class ActivityOptions { new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".NotificationActivity"); + public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity"; + public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName( + FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity"); + public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp"; public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java new file mode 100644 index 000000000000..d60143cdf40a --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java @@ -0,0 +1,22 @@ +/* + * 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.service.voice.VoiceInteractionService; + +public class AssistantInteractionService extends VoiceInteractionService { +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java new file mode 100644 index 000000000000..d2c9b37704b8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java @@ -0,0 +1,53 @@ +/* + * 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.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.view.View; +import android.view.Window; + +public class AssistantInteractionSession extends VoiceInteractionSession { + + public AssistantInteractionSession(Context context) { + super(context); + } + + @Override + public void onCreate() { + Window window = getWindow().getWindow(); + if (window != null) { + window.getDecorView().setBackgroundColor(Color.TRANSPARENT); + + } + View rootView = getLayoutInflater().inflate(R.layout.assistant_session, null); + setContentView(rootView); + setUiEnabled(false); + } + + @Override + public void onShow(Bundle args, int showFlags) { + setUiEnabled(true); + } + + @Override + public void onHide() { + setUiEnabled(false); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java new file mode 100644 index 000000000000..4d6125c9a5d8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java @@ -0,0 +1,28 @@ +/* + * 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.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.service.voice.VoiceInteractionSessionService; + +public class AssistantInteractionSessionService extends VoiceInteractionSessionService { + @Override + public VoiceInteractionSession onNewSession(Bundle args) { + return new AssistantInteractionSession(this); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java new file mode 100644 index 000000000000..68aae4520fe9 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java @@ -0,0 +1,37 @@ +/* + * 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.content.Intent; +import android.speech.RecognitionService; + +public class AssistantRecognitionService extends RecognitionService { + @Override + protected void onStartListening(Intent recognizerIntent, Callback listener) { + + } + + @Override + protected void onCancel(Callback listener) { + + } + + @Override + protected void onStopListening(Callback listener) { + + } +} |