diff options
113 files changed, 2286 insertions, 1040 deletions
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java index dcabf57cb8a9..df8a50a1fa5c 100644 --- a/core/java/android/animation/AnimationHandler.java +++ b/core/java/android/animation/AnimationHandler.java @@ -19,10 +19,10 @@ package android.animation; import android.os.SystemClock; import android.os.SystemProperties; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; import android.view.Choreographer; +import java.lang.ref.WeakReference; import java.util.ArrayList; /** @@ -78,7 +78,7 @@ public class AnimationHandler { * store visible (foreground) requestors; if the set size reaches zero, there are no * objects in the foreground and it is time to pause animators. */ - private final ArraySet<Object> mAnimatorRequestors = new ArraySet<>(); + private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>(); private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override @@ -141,19 +141,9 @@ public class AnimationHandler { * tracking obsolete+enabled requestors. */ public static void removeRequestor(Object requestor) { - getInstance().removeRequestorImpl(requestor); - } - - private void removeRequestorImpl(Object requestor) { - // Also request disablement, in case that requestor was the sole object keeping - // animators un-paused - requestAnimatorsEnabled(false, requestor); - mAnimatorRequestors.remove(requestor); + getInstance().requestAnimatorsEnabledImpl(false, requestor); if (LOCAL_LOGV) { - Log.v(TAG, "removeRequestorImpl for " + requestor); - for (int i = 0; i < mAnimatorRequestors.size(); ++i) { - Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i)); - } + Log.v(TAG, "removeRequestor for " + requestor); } } @@ -173,10 +163,36 @@ public class AnimationHandler { private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) { boolean wasEmpty = mAnimatorRequestors.isEmpty(); setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties()); - if (enable) { - mAnimatorRequestors.add(requestor); - } else { - mAnimatorRequestors.remove(requestor); + synchronized (mAnimatorRequestors) { + // Only store WeakRef objects to avoid leaks + if (enable) { + // First, check whether such a reference is already on the list + WeakReference<Object> weakRef = null; + for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) { + WeakReference<Object> ref = mAnimatorRequestors.get(i); + Object referent = ref.get(); + if (referent == requestor) { + weakRef = ref; + } else if (referent == null) { + // Remove any reference that has been cleared + mAnimatorRequestors.remove(i); + } + } + if (weakRef == null) { + weakRef = new WeakReference<>(requestor); + mAnimatorRequestors.add(weakRef); + } + } else { + for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) { + WeakReference<Object> ref = mAnimatorRequestors.get(i); + Object referent = ref.get(); + if (referent == requestor || referent == null) { + // remove requested item or item that has been cleared + mAnimatorRequestors.remove(i); + } + } + // If a reference to the requestor wasn't in the list, nothing to remove + } } if (!sAnimatorPausingEnabled) { // Resume any animators that have been paused in the meantime, otherwise noop @@ -198,9 +214,12 @@ public class AnimationHandler { } } if (LOCAL_LOGV) { - Log.v(TAG, enable ? "enable" : "disable" + " animators for " + requestor); + Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor + + " with pauseDelay of " + Animator.getBackgroundPauseDelay()); for (int i = 0; i < mAnimatorRequestors.size(); ++i) { - Log.v(TAG, "animatorRequesters " + i + " = " + mAnimatorRequestors.valueAt(i)); + Log.v(TAG, "animatorRequestors " + i + " = " + + mAnimatorRequestors.get(i) + " with referent " + + mAnimatorRequestors.get(i).get()); } } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 8bc11cbc61de..f94e0313771e 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -559,18 +559,20 @@ public final class DisplayManager { * @see #DISPLAY_CATEGORY_PRESENTATION */ public Display[] getDisplays(String category) { - final int[] displayIds = mGlobal.getDisplayIds(); + boolean includeDisabled = (category != null + && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)); + final int[] displayIds = mGlobal.getDisplayIds(includeDisabled); synchronized (mLock) { try { - if (category == null - || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) { - addAllDisplaysLocked(mTempDisplays, displayIds); - } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) { + if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) { addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL); + } else if (category == null + || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) { + addAllDisplaysLocked(mTempDisplays, displayIds); } return mTempDisplays.toArray(new Display[mTempDisplays.size()]); } finally { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 74356ddecc76..63dc7c7ed661 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -206,6 +206,16 @@ public final class DisplayManagerGlobal { */ @UnsupportedAppUsage public int[] getDisplayIds() { + return getDisplayIds(/* includeDisabled= */ false); + } + + /** + * Gets all currently valid logical display ids. + * + * @param includeDisabled True if the returned list of displays includes disabled displays. + * @return An array containing all display ids. + */ + public int[] getDisplayIds(boolean includeDisabled) { try { synchronized (mLock) { if (USE_CACHE) { @@ -214,7 +224,7 @@ public final class DisplayManagerGlobal { } } - int[] displayIds = mDm.getDisplayIds(); + int[] displayIds = mDm.getDisplayIds(includeDisabled); if (USE_CACHE) { mDisplayIdCache = displayIds; } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index ca3e58094400..a4115d178f6f 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -36,7 +36,7 @@ import android.view.Surface; interface IDisplayManager { @UnsupportedAppUsage DisplayInfo getDisplayInfo(int displayId); - int[] getDisplayIds(); + int[] getDisplayIds(boolean includeDisabled); boolean isUidPresentOnDisplay(int uid, int displayId); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8f4a836b6861..b4698dee6019 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1409,7 +1409,11 @@ public final class ViewRootImpl implements ViewParent, mFirstPostImeInputStage = earlyPostImeStage; mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix; - AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); + if (!mRemoved || !mAppVisible) { + AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); + } else if (LOCAL_LOGV) { + Log.v(mTag, "setView() enabling visibility when removed"); + } } } } @@ -1747,7 +1751,12 @@ public final class ViewRootImpl implements ViewParent, if (!mAppVisible) { WindowManagerGlobal.trimForeground(); } - AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); + // Only enable if the window is not already removed (via earlier call to doDie()) + if (!mRemoved || !mAppVisible) { + AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); + } else if (LOCAL_LOGV) { + Log.v(mTag, "handleAppVisibility() enabling visibility when removed"); + } } } diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java index 573db0d58625..384dacfe89ed 100644 --- a/core/java/android/window/StartingWindowRemovalInfo.java +++ b/core/java/android/window/StartingWindowRemovalInfo.java @@ -61,6 +61,12 @@ public final class StartingWindowRemovalInfo implements Parcelable { */ public boolean deferRemoveForIme; + /** + * The rounded corner radius + * @hide + */ + public float roundedCornerRadius; + public StartingWindowRemovalInfo() { } @@ -80,6 +86,7 @@ public final class StartingWindowRemovalInfo implements Parcelable { mainFrame = source.readTypedObject(Rect.CREATOR); playRevealAnimation = source.readBoolean(); deferRemoveForIme = source.readBoolean(); + roundedCornerRadius = source.readFloat(); } @Override @@ -89,6 +96,7 @@ public final class StartingWindowRemovalInfo implements Parcelable { dest.writeTypedObject(mainFrame, flags); dest.writeBoolean(playRevealAnimation); dest.writeBoolean(deferRemoveForIme); + dest.writeFloat(roundedCornerRadius); } @Override @@ -96,6 +104,7 @@ public final class StartingWindowRemovalInfo implements Parcelable { return "StartingWindowRemovalInfo{taskId=" + taskId + " frame=" + mainFrame + " playRevealAnimation=" + playRevealAnimation + + " roundedCornerRadius=" + roundedCornerRadius + " deferRemoveForIme=" + deferRemoveForIme + "}"; } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 56e1a872423d..2cf41bbfffc1 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -328,4 +328,7 @@ oneway interface IStatusBar /** Shows rear display educational dialog */ void showRearDisplayDialog(int currentBaseState); + + /** Called when requested to go to fullscreen from the active split app. */ + void goToFullscreenFromSplit(); } diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 343e10e847fa..fe3e88611c6a 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string> <string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index bf7b638587f8..0f74aab78924 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string> <string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index ae2bfffd7f24..9fbf0a070019 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string> <string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 4bde170710f2..ad086bb1f712 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string> <string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 6081a1cb6c60..7b2b7c5dd01e 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string> <string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 0a77c64bf6ed..8edcddff14e2 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 0a77c64bf6ed..8edcddff14e2 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string> <string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index 2f734250438a..fee34eb28725 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string> <string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index 2650b761e7c1..166041d6d6d8 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -33,8 +33,7 @@ <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string> <string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string> <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string> - <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) --> - <skip /> + <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string> <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string> <string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index abc4024bc290..17ba246910d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -64,6 +64,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.concurrent.Executor; /** @@ -258,18 +259,36 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll @NonNull private WindowContainerTransaction bringDesktopAppsToFront() { - ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(); ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size()); - ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>(); + + final List<RunningTaskInfo> taskInfos = new ArrayList<>(); for (Integer taskId : activeTasks) { RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId); if (taskInfo != null) { taskInfos.add(taskInfo); } } - // Order by lastActiveTime, descending - taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime)); - WindowContainerTransaction wct = new WindowContainerTransaction(); + + if (taskInfos.isEmpty()) { + return wct; + } + + final boolean allActiveTasksAreVisible = taskInfos.stream() + .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId)); + if (allActiveTasksAreVisible) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "bringDesktopAppsToFront: active tasks are already in front, skipping."); + return wct; + } + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "bringDesktopAppsToFront: reordering all active tasks to the front"); + final List<Integer> allTasksInZOrder = + mDesktopModeTaskRepository.getFreeformTasksInZOrder(); + // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last + // in the WCT. + taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId))); for (RunningTaskInfo task : taskInfos) { wct.reorder(task.token, true); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index b7749fc4c3d4..600ccc17ecaa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -33,6 +33,8 @@ class DesktopModeTaskRepository { */ private val activeTasks = ArraySet<Int>() private val visibleTasks = ArraySet<Int>() + // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). + private val freeformTasksInZOrder = mutableListOf<Int>() private val activeTasksListeners = ArraySet<ActiveTasksListener>() // Track visible tasks separately because a task may be part of the desktop but not visible. private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>() @@ -101,6 +103,13 @@ class DesktopModeTaskRepository { } /** + * Whether a task is visible. + */ + fun isVisibleTask(taskId: Int): Boolean { + return visibleTasks.contains(taskId) + } + + /** * Get a set of the active tasks */ fun getActiveTasks(): ArraySet<Int> { @@ -108,6 +117,13 @@ class DesktopModeTaskRepository { } /** + * Get a list of freeform tasks, ordered from top-bottom (top at index 0). + */ + fun getFreeformTasksInZOrder(): List<Int> { + return freeformTasksInZOrder + } + + /** * Updates whether a freeform task with this id is visible or not and notifies listeners. */ fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) { @@ -127,6 +143,23 @@ class DesktopModeTaskRepository { } /** + * Add (or move if it already exists) the task to the top of the ordered list. + */ + fun addOrMoveFreeformTaskToTop(taskId: Int) { + if (freeformTasksInZOrder.contains(taskId)) { + freeformTasksInZOrder.remove(taskId) + } + freeformTasksInZOrder.add(0, taskId) + } + + /** + * Remove the task from the ordered list. + */ + fun removeFreeformTask(taskId: Int) { + freeformTasksInZOrder.remove(taskId) + } + + /** * Defines interface for classes that can listen to changes for active tasks in desktop mode. */ interface ActiveTasksListener { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 44bcdb2d5de5..8a9b74fd72b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -38,7 +38,8 @@ import java.util.Optional; * {@link ShellTaskOrganizer.TaskListener} for {@link * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}. */ -public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { +public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, + ShellTaskOrganizer.FocusListener { private static final String TAG = "FreeformTaskListener"; private final ShellTaskOrganizer mShellTaskOrganizer; @@ -67,6 +68,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { private void onInit() { mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM); + if (DesktopModeStatus.IS_SUPPORTED) { + mShellTaskOrganizer.addFocusListener(this); + } } @Override @@ -86,13 +90,16 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { t.apply(); } - if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) { + if (DesktopModeStatus.IS_SUPPORTED) { mDesktopModeTaskRepository.ifPresent(repository -> { - if (repository.addActiveTask(taskInfo.taskId)) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "Adding active freeform task: #%d", taskInfo.taskId); + repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); + if (taskInfo.isVisible) { + if (repository.addActiveTask(taskInfo.taskId)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "Adding active freeform task: #%d", taskInfo.taskId); + } + repository.updateVisibleFreeformTasks(taskInfo.taskId, true); } - repository.updateVisibleFreeformTasks(taskInfo.taskId, true); }); } } @@ -105,6 +112,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { if (DesktopModeStatus.IS_SUPPORTED) { mDesktopModeTaskRepository.ifPresent(repository -> { + repository.removeFreeformTask(taskInfo.taskId); if (repository.removeActiveTask(taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); @@ -140,6 +148,18 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { } @Override + public void onFocusTaskChanged(RunningTaskInfo taskInfo) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, + "Freeform Task Focus Changed: #%d focused=%b", + taskInfo.taskId, taskInfo.isFocused); + if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isFocused) { + mDesktopModeTaskRepository.ifPresent(repository -> { + repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); + }); + } + } + + @Override public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { b.setParent(findTaskSurface(taskId)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index f9172ba183de..db0f0bf6fda8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -342,6 +342,16 @@ public class RecentTasksController implements TaskStackListenerCallback, } /** + * Returns the top running leaf task. + */ + @Nullable + public ActivityManager.RunningTaskInfo getTopRunningTask() { + List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1, + false /* filterOnlyVisibleRecents */); + return tasks.isEmpty() ? null : tasks.get(0); + } + + /** * Find the background task that match the given component. */ @Nullable @@ -367,6 +377,8 @@ public class RecentTasksController implements TaskStackListenerCallback, public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); + pw.println(prefix + " mListener=" + mListener); + pw.println(prefix + "Tasks:"); ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < recentTasks.size(); i++) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index d86aadc996e3..2f2bc77b804b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -73,6 +73,9 @@ public interface SplitScreen { /** Called when device waking up finished. */ void onFinishedWakingUp(); + /** Called when requested to go to fullscreen from the current active split app. */ + void goToFullscreenFromSplit(); + /** Get a string representation of a stage type */ static String stageTypeToString(@StageType int stage) { switch (stage) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index a79ac45228ca..400039b32618 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -123,6 +123,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public static final int EXIT_REASON_SCREEN_LOCKED = 7; public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8; public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; + public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10; @IntDef(value = { EXIT_REASON_UNKNOWN, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, @@ -134,6 +135,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, EXIT_REASON_SCREEN_LOCKED, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP, EXIT_REASON_CHILD_TASK_ENTER_PIP, + EXIT_REASON_FULLSCREEN_SHORTCUT, }) @Retention(RetentionPolicy.SOURCE) @interface ExitReason{} @@ -315,10 +317,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator; } - public ActivityManager.RunningTaskInfo getFocusingTaskInfo() { - return mStageCoordinator.getFocusingTaskInfo(); - } - public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) { return mStageCoordinator.isValidToEnterSplitScreen(taskInfo); } @@ -422,6 +420,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.unregisterSplitScreenListener(listener); } + public void goToFullscreenFromSplit() { + mStageCoordinator.goToFullscreenFromSplit(); + } + public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { final int[] result = new int[1]; IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { @@ -628,9 +630,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, if (!isSplitScreenVisible()) { // Split screen is not yet activated, check if the current top running task is valid to // split together. - final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo(); - if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) { - return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity); + final ActivityManager.RunningTaskInfo topRunningTask = mRecentTasksOptional + .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null); + if (topRunningTask != null && isValidToEnterSplitScreen(topRunningTask)) { + return Objects.equals(topRunningTask.baseIntent.getComponent(), launchingActivity); } return false; } @@ -863,9 +866,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void onFinishedWakingUp() { - mMainExecutor.execute(() -> { - SplitScreenController.this.onFinishedWakingUp(); - }); + mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp); + } + + @Override + public void goToFullscreenFromSplit() { + mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit); } } @@ -921,33 +927,25 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void exitSplitScreen(int toTopTaskId) { executeRemoteCallWithTaskPermission(mController, "exitSplitScreen", - (controller) -> { - controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN); - }); + (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN)); } @Override public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide", - (controller) -> { - controller.exitSplitScreenOnHide(exitSplitScreenOnHide); - }); + (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide)); } @Override public void removeFromSideStage(int taskId) { executeRemoteCallWithTaskPermission(mController, "removeFromSideStage", - (controller) -> { - controller.removeFromSideStage(taskId); - }); + (controller) -> controller.removeFromSideStage(taskId)); } @Override public void startTask(int taskId, int position, @Nullable Bundle options) { executeRemoteCallWithTaskPermission(mController, "startTask", - (controller) -> { - controller.startTask(taskId, position, options); - }); + (controller) -> controller.startTask(taskId, position, options)); } @Override @@ -1039,19 +1037,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void startShortcut(String packageName, String shortcutId, int position, @Nullable Bundle options, UserHandle user, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcut", - (controller) -> { - controller.startShortcut(packageName, shortcutId, position, options, user, - instanceId); - }); + (controller) -> controller.startShortcut(packageName, shortcutId, position, + options, user, instanceId)); } @Override public void startIntent(PendingIntent intent, Intent fillInIntent, int position, @Nullable Bundle options, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntent", - (controller) -> { - controller.startIntent(intent, fillInIntent, position, options, instanceId); - }); + (controller) -> controller.startIntent(intent, fillInIntent, position, options, + instanceId)); } @Override 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 4cb76230606f..aa0512b64a16 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 @@ -49,6 +49,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASO import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; @@ -150,7 +151,7 @@ import java.util.Optional; */ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler, - ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener { + ShellTaskOrganizer.TaskListener { private static final String TAG = StageCoordinator.class.getSimpleName(); @@ -186,8 +187,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final Rect mTempRect1 = new Rect(); private final Rect mTempRect2 = new Rect(); - private ActivityManager.RunningTaskInfo mFocusingTaskInfo; - /** * A single-top root task which the split divider attached to. */ @@ -304,7 +303,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); - mTaskOrganizer.addFocusListener(this); mSplitUnsupportedToast = Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); } @@ -455,8 +453,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } /** Launches an activity into split by legacy transition. */ - void startIntentLegacy(PendingIntent intent, Intent fillInIntent, - @SplitPosition int position, @Nullable Bundle options) { + void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, + @Nullable Bundle options) { + final boolean isEnteringSplit = !isSplitActive(); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); @@ -466,22 +465,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { - if (apps == null || apps.length == 0) { - if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { - mMainExecutor.execute(() -> - exitSplitScreen(mMainStage.getChildCount() == 0 - ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); - mSplitUnsupportedToast.show(); + if (isEnteringSplit) { + boolean openingToSide = false; + if (apps != null) { + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING + && mSideStage.containsTask(apps[i].taskId)) { + openingToSide = true; + break; + } + } + } + if (!openingToSide) { + mMainExecutor.execute(() -> exitSplitScreen( + mSideStage.getChildCount() == 0 ? mMainStage : mSideStage, + EXIT_REASON_UNKNOWN)); } - - // Do nothing when the animation was cancelled. - t.apply(); - return; } - for (int i = 0; i < apps.length; ++i) { - if (apps[i].mode == MODE_OPENING) { - t.show(apps[i].leash); + if (apps != null) { + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) { + t.show(apps[i].leash); + } } } t.apply(); @@ -503,7 +509,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If split still not active, apply windows bounds first to avoid surface reset to // wrong pos by SurfaceAnimator from wms. - if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) { + if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) { updateWindowBounds(mSplitLayout, wct); } @@ -1110,15 +1116,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * Exits the split screen by finishing one of the tasks. */ protected void exitStage(@SplitPosition int stageToClose) { - if (ENABLE_SHELL_TRANSITIONS) { - StageTaskListener stageToTop = mSideStagePosition == stageToClose - ? mMainStage - : mSideStage; - exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED); - } else { - boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT; - mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED); - } + mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT, + EXIT_REASON_APP_FINISHED); } /** @@ -1152,6 +1151,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP: // User has unlocked the device after folded case EXIT_REASON_DEVICE_FOLDED: + // The device is folded + case EXIT_REASON_FULLSCREEN_SHORTCUT: + // User has used a keyboard shortcut to go back to fullscreen from split return true; default: return false; @@ -1615,15 +1617,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()); } - ActivityManager.RunningTaskInfo getFocusingTaskInfo() { - return mFocusingTaskInfo; - } - - @Override - public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { - mFocusingTaskInfo = taskInfo; - } - @Override public void onSnappedToDismiss(boolean bottomOrRight, int reason) { final boolean mainStageToTop = @@ -2123,6 +2116,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + public void goToFullscreenFromSplit() { + boolean leftOrTop; + if (mSideStage.isFocused()) { + leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); + } else { + leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT); + } + mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT); + } + /** Synchronize split-screen state with transition and make appropriate preparations. */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java index 8bba44049c88..20da8773f387 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java @@ -50,13 +50,17 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { private final float mIconStartAlpha; private final float mBrandingStartAlpha; private final TransactionPool mTransactionPool; + // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell + private final float mRoundedCornerRadius; private Runnable mFinishCallback; SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash, - Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) { + Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish, + float roundedCornerRadius) { mSplashScreenView = view; mFirstWindowSurface = leash; + mRoundedCornerRadius = roundedCornerRadius; if (frame != null) { mFirstWindowFrame.set(frame); } @@ -97,7 +101,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay, - mAppRevealDuration, this); + mAppRevealDuration, this, mRoundedCornerRadius); } private void reset() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java index 3098e55ec78b..a7e4385b60c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java @@ -63,23 +63,39 @@ public class SplashScreenExitAnimationUtils { /** * Creates and starts the animator to fade out the icon, reveal the app, and shift up main - * window. - * @hide + * window with rounded corner radius. */ - public static void startAnimations(ViewGroup splashScreenView, + static void startAnimations(ViewGroup splashScreenView, SurfaceControl firstWindowSurface, int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, - int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) { + int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener, + float roundedCornerRadius) { ValueAnimator animator = createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration, - animatorListener); + animatorListener, roundedCornerRadius); animator.start(); } /** + * Creates and starts the animator to fade out the icon, reveal the app, and shift up main + * window. + * @hide + */ + public static void startAnimations(ViewGroup splashScreenView, + SurfaceControl firstWindowSurface, int mainWindowShiftLength, + TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, + int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, + int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) { + startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength, + transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration, + iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration, + animatorListener, 0f /* roundedCornerRadius */); + } + + /** * Creates the animator to fade out the icon, reveal the app, and shift up main window. * @hide */ @@ -87,7 +103,8 @@ public class SplashScreenExitAnimationUtils { SurfaceControl firstWindowSurface, int mMainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, - int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) { + int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener, + float roundedCornerRadius) { // reveal app final float transparentRatio = 0.8f; final int globalHeight = splashScreenView.getHeight(); @@ -124,7 +141,7 @@ public class SplashScreenExitAnimationUtils { shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView, firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame, - mMainWindowShiftLength); + mMainWindowShiftLength, roundedCornerRadius); } ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); @@ -289,8 +306,8 @@ public class SplashScreenExitAnimationUtils { public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView, SurfaceControl firstWindowSurface, ViewGroup splashScreenView, TransactionPool transactionPool, Rect firstWindowFrame, - int mainWindowShiftLength) { - mFromYDelta = fromYDelta; + int mainWindowShiftLength, float roundedCornerRadius) { + mFromYDelta = fromYDelta - roundedCornerRadius; mToYDelta = toYDelta; mOccludeHoleView = occludeHoleView; mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 6ce981e25f5e..839d56a43222 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -993,10 +993,11 @@ public class SplashscreenContentDrawer { * Create and play the default exit animation for splash screen view. */ void applyExitAnimation(SplashScreenView view, SurfaceControl leash, - Rect frame, Runnable finishCallback, long createTime) { + Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) { final Runnable playAnimation = () -> { final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext, - view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback); + view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback, + roundedCornerRadius); animation.startAnimations(); }; if (view.getIconView() == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index a0e176c7ea68..053491e3a3ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -645,7 +645,7 @@ public class StartingSurfaceDrawer { mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, removalInfo.windowAnimationLeash, removalInfo.mainFrame, () -> removeWindowInner(record.mDecorView, true), - record.mCreateTime); + record.mCreateTime, removalInfo.roundedCornerRadius); } else { // the SplashScreenView has been copied to client, hide the view to skip // default exit animation diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index e40db4e4dcf2..e7036c726a27 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -75,10 +75,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final SyncTransactionQueue mSyncQueue; private FreeformTaskTransitionStarter mTransitionStarter; private DesktopModeController mDesktopModeController; - private EventReceiver mEventReceiver; - private InputMonitor mInputMonitor; private boolean mTransitionDragActive; + private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); + private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory(); @@ -150,8 +150,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { @Override public void onTaskInfoChanged(RunningTaskInfo taskInfo) { final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); + if (decoration == null) return; + int oldDisplayId = decoration.mDisplay.getDisplayId(); + if (taskInfo.displayId != oldDisplayId) { + removeTaskFromEventReceiver(oldDisplayId); + incrementEventReceiverTasks(taskInfo.displayId); + } + decoration.relayout(taskInfo); } @@ -195,6 +202,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) return; decoration.close(); + int displayId = taskInfo.displayId; + if (mEventReceiversByDisplay.contains(displayId)) { + EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId); + removeTaskFromEventReceiver(displayId); + } } private class CaptionTouchEventListener implements @@ -329,8 +341,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { // InputEventReceiver to listen for touch input outside of caption bounds class EventReceiver extends InputEventReceiver { - EventReceiver(InputChannel channel, Looper looper) { + private InputMonitor mInputMonitor; + private int mTasksOnDisplay; + EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) { super(channel, looper); + mInputMonitor = inputMonitor; + mTasksOnDisplay = 1; } @Override @@ -338,15 +354,62 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { boolean handled = false; if (event instanceof MotionEvent) { handled = true; - CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event); + CaptionWindowDecorViewModel.this + .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor); } finishInputEvent(event, handled); } + + @Override + public void dispose() { + if (mInputMonitor != null) { + mInputMonitor.dispose(); + mInputMonitor = null; + } + super.dispose(); + } + + private void incrementTaskNumber() { + mTasksOnDisplay++; + } + + private void decrementTaskNumber() { + mTasksOnDisplay--; + } + + private int getTasksOnDisplay() { + return mTasksOnDisplay; + } + } + + /** + * Check if an EventReceiver exists on a particular display. + * If it does, increment its task count. Otherwise, create one for that display. + * @param displayId the display to check against + */ + private void incrementEventReceiverTasks(int displayId) { + if (mEventReceiversByDisplay.contains(displayId)) { + EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId); + eventReceiver.incrementTaskNumber(); + } else { + createInputChannel(displayId); + } + } + + // If all tasks on this display are gone, we don't need to monitor its input. + private void removeTaskFromEventReceiver(int displayId) { + if (!mEventReceiversByDisplay.contains(displayId)) return; + EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId); + if (eventReceiver == null) return; + eventReceiver.decrementTaskNumber(); + if (eventReceiver.getTasksOnDisplay() == 0) { + disposeInputChannel(displayId); + } } class EventReceiverFactory { - EventReceiver create(InputChannel channel, Looper looper) { - return new EventReceiver(channel, looper); + EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) { + return new EventReceiver(inputMonitor, channel, looper); } } @@ -355,14 +418,14 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * * @param ev the {@link MotionEvent} received by {@link EventReceiver} */ - private void handleReceivedMotionEvent(MotionEvent ev) { + private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { if (!DesktopModeStatus.isActive(mContext)) { handleCaptionThroughStatusBar(ev); } handleEventOutsideFocusedCaption(ev); // Prevent status bar from reacting to a caption drag. if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) { - mInputMonitor.pilferPointers(); + inputMonitor.pilferPointers(); } } @@ -381,6 +444,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } } + /** * Perform caption actions if not able to through normal means. * Turn on desktop mode if handle is dragged below status bar. @@ -434,6 +498,22 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { return focusedDecor; } + private void createInputChannel(int displayId) { + InputManager inputManager = mInputManagerSupplier.get(); + InputMonitor inputMonitor = + inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId()); + EventReceiver eventReceiver = mEventReceiverFactory.create( + inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper()); + mEventReceiversByDisplay.put(displayId, eventReceiver); + } + + private void disposeInputChannel(int displayId) { + EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId); + if (eventReceiver != null) { + eventReceiver.dispose(); + } + } + private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; return DesktopModeStatus.IS_SUPPORTED @@ -472,14 +552,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setDragResizeCallback(taskPositioner); windowDecoration.relayout(taskInfo, startT, finishT); - if (mInputMonitor == null) { - InputManager inputManager = mInputManagerSupplier.get(); - mInputMonitor = - inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId()); - mEventReceiver = - mEventReceiverFactory.create( - mInputMonitor.getInputChannel(), Looper.myLooper()); - } + incrementEventReceiverTasks(taskInfo.displayId); } private class DragStartListenerImpl implements TaskPositioner.DragStartListener { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index 89bafcb6b2f4..01584a067cc2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -50,6 +50,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction.Change; +import android.window.WindowContainerTransaction.HierarchyOp; import androidx.test.filters.SmallTest; @@ -222,25 +223,29 @@ public class DesktopModeControllerTest extends ShellTestCase { // Check that there are hierarchy changes for home task and visible task assertThat(wct.getHierarchyOps()).hasSize(2); // First show home task - WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0); + HierarchyOp op1 = wct.getHierarchyOps().get(0); assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder()); // Then visible task on top of it - WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1); + HierarchyOp op2 = wct.getHierarchyOps().get(1); assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder()); } @Test - public void testShowDesktopApps() { - // Set up two active tasks on desktop + public void testShowDesktopApps_allAppsInvisible_bringsToFront() { + // Set up two active tasks on desktop, task2 is on top of task1. RunningTaskInfo freeformTask1 = createFreeformTask(); - freeformTask1.lastActiveTime = 100; - RunningTaskInfo freeformTask2 = createFreeformTask(); - freeformTask2.lastActiveTime = 200; mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks( + freeformTask1.taskId, false /* visible */); + RunningTaskInfo freeformTask2 = createFreeformTask(); mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks( + freeformTask2.taskId, false /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn( freeformTask1); when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn( @@ -248,27 +253,66 @@ public class DesktopModeControllerTest extends ShellTestCase { // Run show desktop apps logic mController.showDesktopApps(); - ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass( - WindowContainerTransaction.class); - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any()); - } else { - verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture()); - } - WindowContainerTransaction wct = wctCaptor.getValue(); + final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); // Check wct has reorder calls assertThat(wct.getHierarchyOps()).hasSize(2); - // Task 2 has activity later, must be first - WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0); + // Task 1 appeared first, must be first reorder to top. + HierarchyOp op1 = wct.getHierarchyOps().get(0); assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder()); + assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder()); + + // Task 2 appeared last, must be last reorder to top. + HierarchyOp op2 = wct.getHierarchyOps().get(1); + assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); + assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder()); + } + + @Test + public void testShowDesktopApps_appsAlreadyVisible_doesNothing() { + final RunningTaskInfo task1 = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); + final RunningTaskInfo task2 = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); + + mController.showDesktopApps(); + + final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); + // No reordering needed. + assertThat(wct.getHierarchyOps()).isEmpty(); + } + + @Test + public void testShowDesktopApps_someAppsInvisible_reordersAll() { + final RunningTaskInfo task1 = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); + final RunningTaskInfo task2 = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); - // Task 1 should be second - WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1); + mController.showDesktopApps(); + + final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); + // Both tasks should be reordered to top, even if one was already visible. + assertThat(wct.getHierarchyOps()).hasSize(2); + final HierarchyOp op1 = wct.getHierarchyOps().get(0); + assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); + assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder()); + final HierarchyOp op2 = wct.getHierarchyOps().get(1); assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); - assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder()); + assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder()); } @Test @@ -355,6 +399,17 @@ public class DesktopModeControllerTest extends ShellTestCase { return arg.getValue(); } + private WindowContainerTransaction getBringAppsToFrontTransaction() { + final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass( + WindowContainerTransaction.class); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any()); + } else { + verify(mShellTaskOrganizer).applyTransaction(arg.capture()); + } + return arg.getValue(); + } + private void assertThatBoundsCleared(Change change) { assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue(); assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index aaa5c8a35acb..1e43a5983821 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -140,6 +140,32 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3) } + @Test + fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() { + repo.addOrMoveFreeformTaskToTop(5) + repo.addOrMoveFreeformTaskToTop(6) + repo.addOrMoveFreeformTaskToTop(7) + + val tasks = repo.getFreeformTasksInZOrder() + assertThat(tasks.size).isEqualTo(3) + assertThat(tasks[0]).isEqualTo(7) + assertThat(tasks[1]).isEqualTo(6) + assertThat(tasks[2]).isEqualTo(5) + } + + @Test + fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() { + repo.addOrMoveFreeformTaskToTop(5) + repo.addOrMoveFreeformTaskToTop(6) + repo.addOrMoveFreeformTaskToTop(7) + + repo.addOrMoveFreeformTaskToTop(6) + + val tasks = repo.getFreeformTasksInZOrder() + assertThat(tasks.size).isEqualTo(3) + assertThat(tasks.first()).isEqualTo(6) + } + class TestListener : DesktopModeTaskRepository.ActiveTasksListener { var activeTaskChangedCalls = 0 override fun onActiveTasksChanged() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 38b75f81171f..f8ded7709c68 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -178,10 +178,10 @@ public class SplitScreenControllerTests extends ShellTestCase { Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); - // Put the same component into focus task - ActivityManager.RunningTaskInfo focusTaskInfo = + // Put the same component to the top running task + ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo(); + doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any()); mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null); @@ -199,10 +199,10 @@ public class SplitScreenControllerTests extends ShellTestCase { Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); - // Put the same component into focus task - ActivityManager.RunningTaskInfo focusTaskInfo = + // Put the same component to the top running task + ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo(); + doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any()); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java index 8b134ed1dfe4..9b37b97a4c24 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java @@ -111,7 +111,7 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase { .create(any(), any(), any(), any(), any(), any(), any(), any()); when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor); - when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver); + when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver); when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel); } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 22944b8fba89..462b90a10aee 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -237,6 +237,28 @@ class AnimatableClockView @JvmOverloads constructor( this.lockScreenColor = lockScreenColor } + fun animateColorChange() { + logBuffer?.log(tag, DEBUG, "animateColorChange") + setTextStyle( + weight = lockScreenWeight, + textSize = -1f, + color = null, /* using current color */ + animate = false, + duration = 0, + delay = 0, + onAnimationEnd = null + ) + setTextStyle( + weight = lockScreenWeight, + textSize = -1f, + color = lockScreenColor, + animate = true, + duration = COLOR_ANIM_DURATION, + delay = 0, + onAnimationEnd = null + ) + } + fun animateAppearOnLockscreen() { logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen") setTextStyle( @@ -350,6 +372,7 @@ class AnimatableClockView @JvmOverloads constructor( * * By passing -1 to weight, the view preserves its current weight. * By passing -1 to textSize, the view preserves its current text size. + * By passing null to color, the view preserves its current color. * * @param weight text weight. * @param textSize font size. @@ -611,6 +634,7 @@ class AnimatableClockView @JvmOverloads constructor( private const val APPEAR_ANIM_DURATION: Long = 350 private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500 private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 + private const val COLOR_ANIM_DURATION: Long = 400 // Constants for the animation private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index e1f21742bf93..c540f0f7d557 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -142,7 +142,7 @@ class DefaultClockController( currentColor = color view.setColors(DOZE_COLOR, color) if (!animations.dozeState.isActive) { - view.animateAppearOnLockscreen() + view.animateColorChange() } } } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml index 898935fc7e99..2cac9c706fe9 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml @@ -21,8 +21,6 @@ android:id="@+id/keyguard_bouncer_user_switcher" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clipChildren="false" - android:clipToPadding="false" android:orientation="vertical" android:gravity="center" android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml index 411fea5dd22d..316ad39a9f05 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml @@ -18,6 +18,8 @@ <TextView android:id="@+id/digit_text" style="@style/Widget.TextView.NumPadKey.Digit" + android:autoSizeMaxTextSize="32sp" + android:autoSizeTextType="uniform" android:layout_width="wrap_content" android:layout_height="wrap_content" /> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index 80628f903e76..f4434e8d0044 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -36,7 +36,4 @@ <integer name="qs_security_footer_maxLines">1</integer> <bool name="config_use_large_screen_shade_header">true</bool> - - <!-- Whether to show the side fps hint while on bouncer --> - <bool name="config_show_sidefps_hint_on_bouncer">true</bool> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7a362040427a..4cda8c7b5328 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -552,7 +552,7 @@ <string name="config_preferredEmergencySosPackage" translatable="false"></string> <!-- Whether to show the side fps hint while on bouncer --> - <bool name="config_show_sidefps_hint_on_bouncer">false</bool> + <bool name="config_show_sidefps_hint_on_bouncer">true</bool> <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">false</bool> diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml index de855e275f5f..c32de70771d0 100644 --- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml +++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml @@ -124,20 +124,9 @@ </KeyFrameSet> </Transition> - <Transition - android:id="@+id/large_screen_header_transition" - app:constraintSetStart="@id/large_screen_header_constraint" - app:constraintSetEnd="@id/large_screen_header_constraint"/> - - <!-- - Placeholder ConstraintSet. They are populated in the controller for this class. - This is needed because there's no easy way to just refer to a `ConstraintSet` file. The - options are either a layout file or inline the ConstraintSets. - --> - <ConstraintSet android:id="@id/qqs_header_constraint"/> - - <ConstraintSet android:id="@id/qs_header_constraint"/> + <Include app:constraintSet="@xml/large_screen_shade_header"/> - <ConstraintSet android:id="@id/large_screen_header_constraint" /> + <Include app:constraintSet="@xml/qs_header"/> + <Include app:constraintSet="@xml/qqs_header"/> </MotionScene> diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index 5d3650ccc8e6..e56e5d557c2f 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -59,7 +59,6 @@ <Layout android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constrainedWidth="true" app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" @@ -75,7 +74,6 @@ <Layout android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constrainedWidth="true" app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintStart_toEndOf="@id/statusIcons" app:layout_constraintEnd_toEndOf="@id/end_guide" @@ -112,5 +110,4 @@ app:layout_constraintHorizontal_bias="1" /> </Constraint> - </ConstraintSet>
\ No newline at end of file diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml index 982c422f1fda..eca2b2acb079 100644 --- a/packages/SystemUI/res/xml/qs_header.xml +++ b/packages/SystemUI/res/xml/qs_header.xml @@ -56,6 +56,7 @@ <Layout android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" + app:layout_constrainedWidth="true" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/space" app:layout_constraintBottom_toBottomOf="parent" @@ -88,7 +89,6 @@ <Layout android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constrainedWidth="true" app:layout_constraintStart_toEndOf="@id/space" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" app:layout_constraintTop_toTopOf="@id/date" diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt index 4a41b3fe2589..5bb9367fa4a5 100644 --- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt +++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt @@ -48,11 +48,13 @@ import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_INIT import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED +import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE import com.android.keyguard.InternalFaceAuthReasons.STARTED_WAKING_UP +import com.android.keyguard.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED import com.android.keyguard.InternalFaceAuthReasons.TRUST_DISABLED import com.android.keyguard.InternalFaceAuthReasons.TRUST_ENABLED import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING @@ -121,6 +123,9 @@ private object InternalFaceAuthReasons { const val FACE_AUTHENTICATED = "Face auth started/stopped because face is authenticated" const val BIOMETRIC_ENABLED = "Face auth started/stopped because biometric is enabled on keyguard" + const val STRONG_AUTH_ALLOWED_CHANGED = "Face auth stopped because strong auth allowed changed" + const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = + "Face auth stopped because non strong biometric allowed changed" } /** @@ -204,7 +209,11 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) : @UiEvent(doc = FACE_AUTHENTICATED) FACE_AUTH_UPDATED_ON_FACE_AUTHENTICATED(1187, FACE_AUTHENTICATED), @UiEvent(doc = BIOMETRIC_ENABLED) - FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED); + FACE_AUTH_UPDATED_BIOMETRIC_ENABLED_ON_KEYGUARD(1188, BIOMETRIC_ENABLED), + @UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED) + FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED), + @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED) + FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED); override fun getId(): Int = this.id diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index e6283b86283b..52ca1668e4c9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -53,17 +53,17 @@ data class KeyguardFaceListenModel( val biometricSettingEnabledForUser: Boolean, val bouncerFullyShown: Boolean, val faceAndFpNotAuthenticated: Boolean, + val faceAuthAllowed: Boolean, val faceDisabled: Boolean, val faceLockedOut: Boolean, - val fpLockedOut: Boolean, val goingToSleep: Boolean, val keyguardAwake: Boolean, val keyguardGoingAway: Boolean, val listeningForFaceAssistant: Boolean, val occludingAppRequestingFaceAuth: Boolean, val primaryUser: Boolean, - val scanningAllowedByStrongAuth: Boolean, val secureCameraLaunched: Boolean, + val supportsDetect: Boolean, val switchingUser: Boolean, val udfpsBouncerShowing: Boolean, val udfpsFingerDown: Boolean, @@ -79,9 +79,8 @@ data class KeyguardActiveUnlockModel( // keep sorted val awakeKeyguard: Boolean, val authInterruptActive: Boolean, - val encryptedOrTimedOut: Boolean, - val fpLockout: Boolean, - val lockDown: Boolean, + val fpLockedOut: Boolean, + val primaryAuthRequired: Boolean, val switchingUser: Boolean, val triggerActiveUnlockForAssistant: Boolean, val userCanDismissLockScreen: Boolean diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 2cc5ccdc3fa1..c985fd7bef82 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -34,6 +34,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Trace; import android.util.AttributeSet; +import android.view.WindowInsets; import android.view.WindowInsetsAnimationControlListener; import android.view.WindowInsetsAnimationController; import android.view.animation.AnimationUtils; @@ -236,4 +237,50 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { return getResources().getString( com.android.internal.R.string.keyguard_accessibility_password_unlock); } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + if (!mPasswordEntry.isFocused() && isVisibleToUser()) { + mPasswordEntry.requestFocus(); + } + return super.onApplyWindowInsets(insets); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + if (isVisibleToUser()) { + showKeyboard(); + } else { + hideKeyboard(); + } + } + } + + /** + * Sends signal to the focused window to show the keyboard. + */ + public void showKeyboard() { + post(() -> { + if (mPasswordEntry.isAttachedToWindow() + && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) { + mPasswordEntry.requestFocus(); + mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime()); + } + }); + } + + /** + * Sends signal to the focused window to hide the keyboard. + */ + public void hideKeyboard() { + post(() -> { + if (mPasswordEntry.isAttachedToWindow() + && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) { + mPasswordEntry.clearFocus(); + mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime()); + } + }); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 195e8f92754d..d221e22a4fcd 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -26,7 +26,6 @@ import android.text.method.TextKeyListener; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup.MarginLayoutParams; -import android.view.WindowInsets; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -200,12 +199,9 @@ public class KeyguardPasswordViewController return; } - mView.post(() -> { - if (mView.isShown()) { - mPasswordEntry.requestFocus(); - mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime()); - } - }); + if (mView.isShown()) { + mView.showKeyboard(); + } } @Override @@ -227,16 +223,12 @@ public class KeyguardPasswordViewController super.onPause(); }); } - if (mPasswordEntry.isAttachedToWindow()) { - mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime()); - } + mView.hideKeyboard(); } @Override public void onStartingToHide() { - if (mPasswordEntry.isAttachedToWindow()) { - mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime()); - } + mView.hideKeyboard(); } private void updateSwitchImeButton() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 5c4126eeb93a..8f3484a0c99b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -221,10 +221,11 @@ public class KeyguardSecurityContainer extends ConstraintLayout { public void onEnd(WindowInsetsAnimation animation) { if (!mDisappearAnimRunning) { endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR); - updateChildren(0 /* translationY */, 1f /* alpha */); } else { endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR); + setAlpha(0f); } + updateChildren(0 /* translationY */, 1f /* alpha */); } private void updateChildren(int translationY, float alpha) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 90ed293c73c9..d1a59cc334ab 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -34,9 +34,9 @@ import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent; +import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP; @@ -65,6 +65,7 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_FACE_AUT import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_ON_KEYGUARD_INIT; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP; +import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING; import static com.android.systemui.DejankUtils.whitelistIpcs; @@ -171,7 +172,6 @@ import java.util.Map.Entry; 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; @@ -1421,6 +1421,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + /** + * Whether the user locked down the device. This doesn't include device policy manager lockdown. + */ public boolean isUserInLockdown(int userId) { return containsFlag(mStrongAuthTracker.getStrongAuthForUser(userId), STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); @@ -1452,7 +1455,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mStrongAuthTracker; } - private void notifyStrongAuthStateChanged(int userId) { + @VisibleForTesting + void notifyStrongAuthAllowedChanged(int userId) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -1460,6 +1464,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onStrongAuthStateChanged(userId); } } + if (userId == getCurrentUser()) { + FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo( + mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())); + + // Strong auth is only reset when primary auth is used to enter the device, + // so we only check whether to stop biometric listening states here + updateBiometricListeningState( + BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED); + } } private void notifyLockedOutStateChanged(BiometricSourceType type) { @@ -1471,8 +1484,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } } - - private void notifyNonStrongBiometricStateChanged(int userId) { + @VisibleForTesting + void notifyNonStrongBiometricAllowedChanged(int userId) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -1480,6 +1493,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onNonStrongBiometricAllowedChanged(userId); } } + if (userId == getCurrentUser()) { + FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo( + mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout( + getCurrentUser()) ? -1 : 1); + + // This is only reset when primary auth is used to enter the device, so we only check + // whether to stop biometric listening states here + updateBiometricListeningState(BIOMETRIC_ACTION_STOP, + FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED); + } } private void dispatchErrorMessage(CharSequence message) { @@ -1825,16 +1848,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { - private final Consumer<Integer> mStrongAuthRequiredChangedCallback; - private final Consumer<Integer> mNonStrongBiometricAllowedChanged; - - public StrongAuthTracker(Context context, - Consumer<Integer> strongAuthRequiredChangedCallback, - Consumer<Integer> nonStrongBiometricAllowedChanged) { + /** + * Updates callbacks when strong auth requirements change. + */ + public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { + public StrongAuthTracker(Context context) { super(context); - mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback; - mNonStrongBiometricAllowedChanged = nonStrongBiometricAllowedChanged; } public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) { @@ -1850,7 +1869,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onStrongAuthRequiredChanged(int userId) { - mStrongAuthRequiredChangedCallback.accept(userId); + notifyStrongAuthAllowedChanged(userId); } // TODO(b/247091681): Renaming the inappropriate onIsNonStrongBiometricAllowedChanged @@ -1858,7 +1877,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Strong-Auth @Override public void onIsNonStrongBiometricAllowedChanged(int userId) { - mNonStrongBiometricAllowedChanged.accept(userId); + notifyNonStrongBiometricAllowedChanged(userId); } } @@ -2019,8 +2038,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mUserTracker = userTracker; mTelephonyListenerManager = telephonyListenerManager; mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); - mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged, - this::notifyNonStrongBiometricStateChanged); + mStrongAuthTracker = new StrongAuthTracker(context); mBackgroundExecutor = backgroundExecutor; mBroadcastDispatcher = broadcastDispatcher; mInteractionJankMonitor = interactionJankMonitor; @@ -2594,24 +2612,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || !mLockPatternUtils.isSecure(user); // Don't trigger active unlock if fp is locked out - final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent; + final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent; // Don't trigger active unlock if primary auth is required - final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); - final boolean isLockDown = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - final boolean isEncryptedOrTimedOut = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); + final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true); final boolean shouldTriggerActiveUnlock = (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard) && !mSwitchingUser && !userCanDismissLockScreen - && !fpLockedout - && !isLockDown - && !isEncryptedOrTimedOut + && !fpLockedOut + && !primaryAuthRequired && !mKeyguardGoingAway && !mSecureCameraLaunched; @@ -2623,9 +2634,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab shouldTriggerActiveUnlock, awakeKeyguard, mAuthInterruptActive, - isEncryptedOrTimedOut, - fpLockedout, - isLockDown, + fpLockedOut, + primaryAuthRequired, mSwitchingUser, triggerActiveUnlockForAssistant, userCanDismissLockScreen)); @@ -2677,7 +2687,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !fingerprintDisabledForUser && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser - && biometricEnabledForUser; + && biometricEnabledForUser + && !isUserInLockdown(user); final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled(); final boolean shouldListenBouncerState = @@ -2739,14 +2750,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive && !statusBarShadeLocked; final int user = getCurrentUser(); - final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); - final boolean isLockDown = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - final boolean isEncryptedOrTimedOut = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); - final boolean fpLockedOut = isFingerprintLockedOut(); + final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE); final boolean canBypass = mKeyguardBypassController != null && mKeyguardBypassController.canBypass(); // There's no reason to ask the HAL for authentication when the user can dismiss the @@ -2754,20 +2758,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // the lock screen even when TrustAgents are keeping the device unlocked. final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass; - // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing. - // Lock-down mode shouldn't scan, since it is more explicit. - boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass - && !mPrimaryBouncerFullyShown); - - // If the device supports face detection (without authentication) and bypass is enabled, - // allow face scanning to happen if the device is in lockdown mode. - // Otherwise, prevent scanning. - final boolean supportsDetectOnly = !mFaceSensorProperties.isEmpty() - && canBypass - && mFaceSensorProperties.get(0).supportsFaceDetection; - if (isLockDown && !supportsDetectOnly) { - strongAuthAllowsScanning = false; - } + // If the device supports face detection (without authentication), if bypass is enabled, + // allow face detection to happen even if stronger auth is required. When face is detected, + // we show the bouncer. However, if the user manually locked down the device themselves, + // never attempt to detect face. + final boolean supportsDetect = !mFaceSensorProperties.isEmpty() + && mFaceSensorProperties.get(0).supportsFaceDetection + && canBypass && !mPrimaryBouncerIsOrWillBeShowing + && !isUserInLockdown(user); + final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect; // If the face or fp has recently been authenticated do not attempt to authenticate again. final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user); @@ -2788,14 +2787,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || mUdfpsBouncerShowing) && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded && !mKeyguardGoingAway && biometricEnabledForUser - && strongAuthAllowsScanning && mIsPrimaryUser + && faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser && (!mSecureCameraLaunched || mOccludingAppRequestingFace) && faceAndFpNotAuthenticated - && !mGoingToSleep - // We only care about fp locked out state and not face because we still trigger - // face auth even when face is locked out to show the user a message that face - // unlock was supposed to run but didn't - && !fpLockedOut; + && !mGoingToSleep; // Aggregate relevant fields for debug logging. maybeLogListenerModelData( @@ -2805,19 +2800,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab shouldListen, mAuthInterruptActive, biometricEnabledForUser, - mPrimaryBouncerFullyShown, + mPrimaryBouncerFullyShown, faceAndFpNotAuthenticated, + faceAuthAllowed, faceDisabledForUser, isFaceLockedOut(), - fpLockedOut, mGoingToSleep, awakeKeyguard, mKeyguardGoingAway, shouldListenForFaceAssistant, mOccludingAppRequestingFace, mIsPrimaryUser, - strongAuthAllowsScanning, mSecureCameraLaunched, + supportsDetect, mSwitchingUser, mUdfpsBouncerShowing, isUdfpsFingerDown, @@ -2921,9 +2916,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // This would need to be updated for multi-sensor devices final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() && mFaceSensorProperties.get(0).supportsFaceDetection; - if (isEncryptedOrLockdown(userId) && supportsFaceDetection) { + if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) { + mLogger.v("startListeningForFace - detect"); mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId); } else { + mLogger.v("startListeningForFace - authenticate"); final boolean isBypassEnabled = mKeyguardBypassController != null && mKeyguardBypassController.isBypassEnabled(); mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index bce7d92cd8fb..5bb586e489e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -116,6 +116,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio KeyguardState.LOCKSCREEN, 0f, TransitionState.STARTED, + KeyguardTransitionRepositoryImpl::class.simpleName!!, ) ) emitTransition( @@ -124,6 +125,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio KeyguardState.LOCKSCREEN, 1f, TransitionState.FINISHED, + KeyguardTransitionRepositoryImpl::class.simpleName!!, ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt index 9b193533805e..f3d2905121bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt @@ -41,17 +41,14 @@ constructor( override fun start() { listenForTransitionToAodFromLockscreen() - listenForTransitionToLockscreenFromAod() + listenForTransitionToLockscreenFromDozeStates() } private fun listenForTransitionToAodFromLockscreen() { scope.launch { keyguardInteractor .dozeTransitionTo(DozeStateModel.DOZE_AOD) - .sample( - keyguardTransitionInteractor.startedKeyguardTransitionStep, - { a, b -> Pair(a, b) } - ) + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) .collect { pair -> val (dozeToAod, lastStartedStep) = pair if (lastStartedStep.to == KeyguardState.LOCKSCREEN) { @@ -68,21 +65,19 @@ constructor( } } - private fun listenForTransitionToLockscreenFromAod() { + private fun listenForTransitionToLockscreenFromDozeStates() { + val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING) scope.launch { keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) - .sample( - keyguardTransitionInteractor.startedKeyguardTransitionStep, - { a, b -> Pair(a, b) } - ) + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) .collect { pair -> val (dozeToAod, lastStartedStep) = pair - if (lastStartedStep.to == KeyguardState.AOD) { + if (canGoToLockscreen.contains(lastStartedStep.to)) { keyguardTransitionRepository.startTransition( TransitionInfo( name, - KeyguardState.AOD, + lastStartedStep.to, KeyguardState.LOCKSCREEN, getAnimator(), ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt index 2a220fcd75a6..dad166f2b5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt @@ -21,9 +21,7 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.util.kotlin.sample @@ -42,9 +40,6 @@ constructor( private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) { - private val wakeAndUnlockModes = - setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING) - override fun start() { scope.launch { keyguardInteractor.biometricUnlockState @@ -52,8 +47,7 @@ constructor( .collect { pair -> val (biometricUnlockState, keyguardState) = pair if ( - keyguardState == KeyguardState.AOD && - wakeAndUnlockModes.contains(biometricUnlockState) + keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState) ) { keyguardTransitionRepository.startTransition( TransitionInfo( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt deleted file mode 100644 index 9cbf9eac686a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt +++ /dev/null @@ -1,94 +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.keyguard.domain.interactor - -import android.animation.ValueAnimator -import com.android.systemui.animation.Interpolators -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.DozeStateModel -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionInfo -import com.android.systemui.util.kotlin.sample -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch - -@SysUISingleton -class DreamingLockscreenTransitionInteractor -@Inject -constructor( - @Application private val scope: CoroutineScope, - private val keyguardInteractor: KeyguardInteractor, - private val keyguardTransitionRepository: KeyguardTransitionRepository, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, -) : TransitionInteractor(DreamingLockscreenTransitionInteractor::class.simpleName!!) { - - override fun start() { - scope.launch { - keyguardInteractor.isDreaming - .sample( - combine( - keyguardInteractor.dozeTransitionModel, - keyguardTransitionInteractor.finishedKeyguardState - ) { a, b -> Pair(a, b) }, - { a, bc -> Triple(a, bc.first, bc.second) } - ) - .collect { triple -> - val (isDreaming, dozeTransitionModel, keyguardState) = triple - // Dozing/AOD and dreaming have overlapping events. If the state remains in - // FINISH, it means that doze mode is not running and DREAMING is ok to - // commence. - if (dozeTransitionModel.to == DozeStateModel.FINISH) { - if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.LOCKSCREEN, - KeyguardState.DREAMING, - getAnimator(), - ) - ) - } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.DREAMING, - KeyguardState.LOCKSCREEN, - getAnimator(), - ) - ) - } - } - } - } - } - - private fun getAnimator(): ValueAnimator { - return ValueAnimator().apply { - setInterpolator(Interpolators.LINEAR) - setDuration(TRANSITION_DURATION_MS) - } - } - - companion object { - private const val TRANSITION_DURATION_MS = 500L - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt deleted file mode 100644 index e34981eabcac..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt +++ /dev/null @@ -1,76 +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.keyguard.domain.interactor - -import android.animation.ValueAnimator -import com.android.systemui.animation.Interpolators -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionInfo -import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep -import com.android.systemui.util.kotlin.sample -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -@SysUISingleton -class DreamingToAodTransitionInteractor -@Inject -constructor( - @Application private val scope: CoroutineScope, - private val keyguardInteractor: KeyguardInteractor, - private val keyguardTransitionRepository: KeyguardTransitionRepository, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, -) : TransitionInteractor("DREAMING->AOD") { - - override fun start() { - scope.launch { - keyguardInteractor.wakefulnessModel - .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) - .collect { pair -> - val (wakefulnessState, keyguardState) = pair - if ( - isSleepingOrStartingToSleep(wakefulnessState) && - keyguardState == KeyguardState.DREAMING - ) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.DREAMING, - KeyguardState.AOD, - getAnimator(), - ) - ) - } - } - } - } - - private fun getAnimator(): ValueAnimator { - return ValueAnimator().apply { - setInterpolator(Interpolators.LINEAR) - setDuration(TRANSITION_DURATION_MS) - } - } - - companion object { - private const val TRANSITION_DURATION_MS = 300L - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt new file mode 100644 index 000000000000..b73ce9e5689c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt @@ -0,0 +1,180 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock +import com.android.systemui.keyguard.shared.model.DozeStateModel +import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) { + + private val canDreamFrom = + setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING) + + override fun start() { + listenForEntryToDreaming() + listenForDreamingToLockscreen() + listenForDreamingToGone() + listenForDreamingToDozing() + } + + private fun listenForEntryToDreaming() { + scope.launch { + keyguardInteractor.isDreaming + .sample( + combine( + keyguardInteractor.dozeTransitionModel, + keyguardTransitionInteractor.finishedKeyguardState, + ::Pair + ), + ::toTriple + ) + .collect { triple -> + val (isDreaming, dozeTransitionModel, keyguardState) = triple + // Dozing/AOD and dreaming have overlapping events. If the state remains in + // FINISH, it means that doze mode is not running and DREAMING is ok to + // commence. + if ( + isDozeOff(dozeTransitionModel.to) && + isDreaming && + canDreamFrom.contains(keyguardState) + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + keyguardState, + KeyguardState.DREAMING, + getAnimator(), + ) + ) + } + } + } + } + + private fun listenForDreamingToLockscreen() { + scope.launch { + keyguardInteractor.isDreaming + .sample( + combine( + keyguardInteractor.dozeTransitionModel, + keyguardTransitionInteractor.startedKeyguardTransitionStep, + ::Pair, + ), + ::toTriple + ) + .collect { triple -> + val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple + if ( + isDozeOff(dozeTransitionModel.to) && + !isDreaming && + lastStartedTransition.to == KeyguardState.DREAMING + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) + ) + } + } + } + } + + private fun listenForDreamingToGone() { + scope.launch { + keyguardInteractor.biometricUnlockState + .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair) + .collect { pair -> + val (biometricUnlockState, keyguardState) = pair + if ( + keyguardState == KeyguardState.DREAMING && + isWakeAndUnlock(biometricUnlockState) + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.GONE, + getAnimator(), + ) + ) + } + } + } + } + + private fun listenForDreamingToDozing() { + scope.launch { + combine( + keyguardInteractor.dozeTransitionModel, + keyguardTransitionInteractor.finishedKeyguardState, + ::Pair + ) + .collect { pair -> + val (dozeTransitionModel, keyguardState) = pair + if ( + dozeTransitionModel.to == DozeStateModel.DOZE && + keyguardState == KeyguardState.DREAMING + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.DOZING, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt index 483041a1b236..a50e75909dd8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt @@ -37,7 +37,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, -) : TransitionInteractor("GONE->AOD") { +) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) { override fun start() { scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index e30e7f6ece11..a2661d76d90d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -46,6 +46,8 @@ constructor( scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } } + scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } } + scope.launch { interactor.finishedKeyguardTransitionStep.collect { logger.i("Finished transition", it) @@ -61,5 +63,9 @@ constructor( scope.launch { interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) } } + + scope.launch { + keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze transition", it) } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index 43dd358e4808..bb8b79a3aa6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -43,8 +43,7 @@ constructor( is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it") is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it") - is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") - is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt index 4100f7a8413a..95d96025cf4a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt @@ -37,15 +37,15 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, -) : TransitionInteractor("LOCKSCREEN->GONE") { +) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) { override fun start() { scope.launch { keyguardInteractor.isKeyguardGoingAway - .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) .collect { pair -> - val (isKeyguardGoingAway, keyguardState) = pair - if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) { + val (isKeyguardGoingAway, lastStartedStep) = pair + if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) { keyguardTransitionRepository.startTransition( TransitionInfo( name, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index dbffeab436a4..5f63ae765854 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -52,13 +52,5 @@ abstract class StartKeyguardTransitionModule { @IntoSet abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor - @Binds - @IntoSet - abstract fun dreamingLockscreen( - impl: DreamingLockscreenTransitionInteractor - ): TransitionInteractor - - @Binds - @IntoSet - abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor + @Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index a2a46d9e3a71..08ad3d5bdbf6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -29,4 +29,6 @@ package com.android.systemui.keyguard.domain.interactor sealed class TransitionInteractor(val name: String) { abstract fun start() + + fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt index db709b476c5d..8fe6309fc005 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt @@ -46,5 +46,14 @@ enum class BiometricUnlockModel { /** When bouncer is visible and will be dismissed. */ DISMISS_BOUNCER, /** Mode in which fingerprint wakes and unlocks the device from a dream. */ - WAKE_AND_UNLOCK_FROM_DREAM, + WAKE_AND_UNLOCK_FROM_DREAM; + + companion object { + private val wakeAndUnlockModes = + setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING) + + fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean { + return wakeAndUnlockModes.contains(model) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt index 7039188d9e96..65b7cf732f9d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt @@ -42,5 +42,11 @@ enum class DozeStateModel { /** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */ DOZE_AOD_PAUSING, /** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */ - DOZE_AOD_DOCKED + DOZE_AOD_DOCKED; + + companion object { + fun isDozeOff(model: DozeStateModel): Boolean { + return model == UNINITIALIZED || model == FINISH + } + } } 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 120f7d673881..b55bedda2dc1 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 @@ -58,6 +58,27 @@ class MediaTttLogger( ) } + /** + * Logs an invalid sender state transition error in trying to update to [desiredState]. + * + * @param currentState the previous state of the chip. + * @param desiredState the new state of the chip. + */ + fun logInvalidStateTransitionError( + currentState: String, + desiredState: String + ) { + buffer.log( + tag, + LogLevel.ERROR, + { + str1 = currentState + str2 = desiredState + }, + { "Cannot display state=$str2 after state=$str1; invalid transition" } + ) + } + /** Logs that we couldn't find information for [packageName]. */ fun logPackageNotFound(packageName: String) { buffer.log( 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 af7317c208ab..1f27582cb7aa 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 @@ -56,7 +56,12 @@ enum class ChipStateSender( R.string.media_move_closer_to_start_cast, transferStatus = TransferStatus.NOT_STARTED, endItem = null, - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == TRANSFER_TO_RECEIVER_TRIGGERED + } + }, /** * A state representing that the two devices are close but not close enough to *end* a cast @@ -70,7 +75,12 @@ enum class ChipStateSender( R.string.media_move_closer_to_end_cast, transferStatus = TransferStatus.NOT_STARTED, endItem = null, - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED + } + }, /** * A state representing that a transfer to the receiver device has been initiated (but not @@ -83,7 +93,13 @@ enum class ChipStateSender( transferStatus = TransferStatus.IN_PROGRESS, endItem = SenderEndItem.Loading, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == TRANSFER_TO_RECEIVER_SUCCEEDED || + nextState == TRANSFER_TO_RECEIVER_FAILED + } + }, /** * A state representing that a transfer from the receiver device and back to this device (the @@ -96,7 +112,13 @@ enum class ChipStateSender( transferStatus = TransferStatus.IN_PROGRESS, endItem = SenderEndItem.Loading, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED || + nextState == TRANSFER_TO_THIS_DEVICE_FAILED + } + }, /** * A state representing that a transfer to the receiver device has been successfully completed. @@ -112,7 +134,13 @@ enum class ChipStateSender( newState = StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED ), - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == ALMOST_CLOSE_TO_START_CAST || + nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED + } + }, /** * A state representing that a transfer back to this device has been successfully completed. @@ -128,7 +156,13 @@ enum class ChipStateSender( newState = StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED ), - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == ALMOST_CLOSE_TO_END_CAST || + nextState == TRANSFER_TO_RECEIVER_TRIGGERED + } + }, /** A state representing that a transfer to the receiver device has failed. */ TRANSFER_TO_RECEIVER_FAILED( @@ -137,7 +171,13 @@ enum class ChipStateSender( R.string.media_transfer_failed, transferStatus = TransferStatus.FAILED, endItem = SenderEndItem.Error, - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == ALMOST_CLOSE_TO_START_CAST || + nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED + } + }, /** A state representing that a transfer back to this device has failed. */ TRANSFER_TO_THIS_DEVICE_FAILED( @@ -146,7 +186,13 @@ enum class ChipStateSender( R.string.media_transfer_failed, transferStatus = TransferStatus.FAILED, endItem = SenderEndItem.Error, - ), + ) { + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState == ALMOST_CLOSE_TO_END_CAST || + nextState == TRANSFER_TO_RECEIVER_TRIGGERED + } + }, /** A state representing that this device is far away from any receiver device. */ FAR_FROM_RECEIVER( @@ -162,6 +208,12 @@ enum class ChipStateSender( throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " + "so its string should never be fetched") } + + override fun isValidNextState(nextState: ChipStateSender): Boolean { + return nextState == FAR_FROM_RECEIVER || + nextState.transferStatus == TransferStatus.NOT_STARTED || + nextState.transferStatus == TransferStatus.IN_PROGRESS + } }; /** @@ -175,6 +227,8 @@ enum class ChipStateSender( return Text.Loaded(context.getString(stringResId!!, otherDeviceName)) } + abstract fun isValidNextState(nextState: ChipStateSender): Boolean + companion object { /** * Returns the sender state enum associated with the given [displayState] from @@ -197,6 +251,31 @@ enum class ChipStateSender( */ @StatusBarManager.MediaTransferSenderState fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt + + /** + * Validates the transition from a chip state to another. + * + * @param currentState is the current state of the chip. + * @param desiredState is the desired state of the chip. + * @return true if the transition from [currentState] to [desiredState] is valid, and false + * otherwise. + */ + fun isValidStateTransition( + currentState: ChipStateSender?, + desiredState: ChipStateSender, + ): Boolean { + // Far from receiver is the default state. + if (currentState == null) { + return FAR_FROM_RECEIVER.isValidNextState(desiredState) + } + + // No change in state is valid. + if (currentState == desiredState) { + return true + } + + return currentState.isValidNextState(desiredState) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index bb7bc6fff99f..e34b0cbdbdc3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -52,6 +52,8 @@ constructor( ) : CoreStartable { private var displayedState: ChipStateSender? = null + // A map to store current chip state per id. + private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf() private val commandQueueCallbacks = object : CommandQueue.Callbacks { @@ -87,9 +89,22 @@ constructor( logger.logStateChangeError(displayState) return } + + val currentState = stateMap[routeInfo.id] + if (!ChipStateSender.isValidStateTransition(currentState, chipState)) { + // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state. + logger.logInvalidStateTransitionError( + currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name, + chipState.name + ) + return + } uiEventLogger.logSenderStateChange(chipState) + stateMap.put(routeInfo.id, chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { + // No need to store the state since it is the default state + stateMap.remove(routeInfo.id) // Return early if we're not displaying a chip anyway val currentDisplayedState = displayedState ?: return @@ -119,7 +134,7 @@ constructor( context, logger, ) - ) + ) { stateMap.remove(routeInfo.id) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 5670b6de3c36..e43d4c8a34eb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -184,8 +184,6 @@ public class InternetTile extends QSTileImpl<SignalState> { int mWifiSignalIconId; @Nullable String mSsid; - boolean mActivityIn; - boolean mActivityOut; @Nullable String mWifiSignalContentDescription; boolean mIsTransient; @@ -203,8 +201,6 @@ public class InternetTile extends QSTileImpl<SignalState> { .append(",mConnected=").append(mConnected) .append(",mWifiSignalIconId=").append(mWifiSignalIconId) .append(",mSsid=").append(mSsid) - .append(",mActivityIn=").append(mActivityIn) - .append(",mActivityOut=").append(mActivityOut) .append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription) .append(",mIsTransient=").append(mIsTransient) .append(",mNoDefaultNetwork=").append(mNoDefaultNetwork) @@ -222,8 +218,6 @@ public class InternetTile extends QSTileImpl<SignalState> { CharSequence mDataContentDescription; int mMobileSignalIconId; int mQsTypeIcon; - boolean mActivityIn; - boolean mActivityOut; boolean mNoSim; boolean mRoaming; boolean mMultipleSubs; @@ -239,8 +233,6 @@ public class InternetTile extends QSTileImpl<SignalState> { .append(",mDataContentDescription=").append(mDataContentDescription) .append(",mMobileSignalIconId=").append(mMobileSignalIconId) .append(",mQsTypeIcon=").append(mQsTypeIcon) - .append(",mActivityIn=").append(mActivityIn) - .append(",mActivityOut=").append(mActivityOut) .append(",mNoSim=").append(mNoSim) .append(",mRoaming=").append(mRoaming) .append(",mMultipleSubs=").append(mMultipleSubs) @@ -271,8 +263,6 @@ public class InternetTile extends QSTileImpl<SignalState> { mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription; mWifiInfo.mEnabled = indicators.enabled; mWifiInfo.mSsid = indicators.description; - mWifiInfo.mActivityIn = indicators.activityIn; - mWifiInfo.mActivityOut = indicators.activityOut; mWifiInfo.mIsTransient = indicators.isTransient; mWifiInfo.mStatusLabel = indicators.statusLabel; refreshState(mWifiInfo); @@ -293,8 +283,6 @@ public class InternetTile extends QSTileImpl<SignalState> { ? indicators.typeContentDescriptionHtml : null; mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon; mCellularInfo.mQsTypeIcon = indicators.qsType; - mCellularInfo.mActivityIn = indicators.activityIn; - mCellularInfo.mActivityOut = indicators.activityOut; mCellularInfo.mRoaming = indicators.roaming; mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1; refreshState(mCellularInfo); @@ -424,8 +412,6 @@ public class InternetTile extends QSTileImpl<SignalState> { state.state = Tile.STATE_ACTIVE; state.dualTarget = true; state.value = cb.mEnabled; - state.activityIn = cb.mEnabled && cb.mActivityIn; - state.activityOut = cb.mEnabled && cb.mActivityOut; final StringBuffer minimalContentDescription = new StringBuffer(); final StringBuffer minimalStateDescription = new StringBuffer(); final Resources r = mContext.getResources(); @@ -499,8 +485,6 @@ public class InternetTile extends QSTileImpl<SignalState> { boolean mobileDataEnabled = mDataController.isMobileDataSupported() && mDataController.isMobileDataEnabled(); state.value = mobileDataEnabled; - state.activityIn = mobileDataEnabled && cb.mActivityIn; - state.activityOut = mobileDataEnabled && cb.mActivityOut; state.expandedAccessibilityClassName = Switch.class.getName(); if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 5e47d6dd5b76..b511b5463cbf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -46,7 +46,6 @@ import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT -import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider @@ -73,11 +72,9 @@ import javax.inject.Named * expansion of the headers in small screen portrait. * * [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the - * [MotionLayout] has 2 transitions: + * [MotionLayout] has one transitions: * * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait * handheld device configuration. - * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all - * other configurations */ @CentralSurfacesScope class LargeScreenShadeHeaderController @Inject constructor( @@ -104,8 +101,6 @@ class LargeScreenShadeHeaderController @Inject constructor( @VisibleForTesting internal val HEADER_TRANSITION_ID = R.id.header_transition @VisibleForTesting - internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition - @VisibleForTesting internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint @VisibleForTesting internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint @@ -120,10 +115,6 @@ class LargeScreenShadeHeaderController @Inject constructor( } } - init { - loadConstraints() - } - private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS) private lateinit var iconManager: StatusBarIconController.TintedIconManager @@ -345,11 +336,11 @@ class LargeScreenShadeHeaderController @Inject constructor( if (header is MotionLayout) { // Use resources.getXml instead of passing the resource id due to bug b/205018300 header.getConstraintSet(QQS_HEADER_CONSTRAINT) - .load(context, resources.getXml(R.xml.qqs_header)) + .load(context, resources.getXml(R.xml.qqs_header)) header.getConstraintSet(QS_HEADER_CONSTRAINT) - .load(context, resources.getXml(R.xml.qs_header)) + .load(context, resources.getXml(R.xml.qs_header)) header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT) - .load(context, resources.getXml(R.xml.large_screen_shade_header)) + .load(context, resources.getXml(R.xml.large_screen_shade_header)) } } @@ -438,7 +429,6 @@ class LargeScreenShadeHeaderController @Inject constructor( } header as MotionLayout if (largeScreenActive) { - header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID) header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header) } else { header.setTransition(HEADER_TRANSITION_ID) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index f52ace7138f2..6a192161fa93 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -200,7 +200,6 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; -import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -466,7 +465,11 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mQsTouchAboveFalsingThreshold; private int mQsFalsingThreshold; - /** Indicates drag starting height when swiping down or up on heads-up notifications */ + /** + * Indicates drag starting height when swiping down or up on heads-up notifications. + * This usually serves as a threshold from when shade expansion should really start. Otherwise + * this value would be height of shade and it will be immediately expanded to some extent. + */ private int mHeadsUpStartHeight; private HeadsUpTouchHelper mHeadsUpTouchHelper; private boolean mListenForHeadsUp; @@ -916,6 +919,7 @@ public final class NotificationPanelViewController implements Dumpable { mQsFrameTranslateController = qsFrameTranslateController; updateUserSwitcherFlags(); mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel; + mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; onFinishInflate(); keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { @@ -933,7 +937,6 @@ public final class NotificationPanelViewController implements Dumpable { unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay); } }); - mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; dumpManager.registerDumpable(this); } @@ -1109,6 +1112,7 @@ public final class NotificationPanelViewController implements Dumpable { mKeyguardStatusViewComponentFactory.build(keyguardStatusView); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + updateClockAppearance(); if (mKeyguardUserSwitcherController != null) { // Try to close the switcher so that callbacks are triggered if necessary. @@ -3412,9 +3416,12 @@ public final class NotificationPanelViewController implements Dumpable { && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll; boolean goingBetweenClosedShadeAndExpandedQs = mQsExpandImmediate || collapsingShadeFromExpandedQs; - // we don't want to update QS expansion when HUN is visible because then the whole shade is - // initially hidden, even though it has non-zero height - if (goingBetweenClosedShadeAndExpandedQs && !mHeadsUpManager.isTrackingHeadsUp()) { + // in split shade we react when HUN is visible only if shade height is over HUN start + // height - which means user is swiping down. Otherwise shade QS will either not show at all + // with HUN movement or it will blink when touching HUN initially + boolean qsShouldExpandWithHeadsUp = !mSplitShadeEnabled + || (!mHeadsUpManager.isTrackingHeadsUp() || expandedHeight > mHeadsUpStartHeight); + if (goingBetweenClosedShadeAndExpandedQs && qsShouldExpandWithHeadsUp) { float qsExpansionFraction; if (mSplitShadeEnabled) { qsExpansionFraction = 1; @@ -4587,55 +4594,6 @@ public final class NotificationPanelViewController implements Dumpable { return new TouchHandler(); } - private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler = - new PhoneStatusBarView.TouchEventHandler() { - @Override - public void onInterceptTouchEvent(MotionEvent event) { - mCentralSurfaces.onTouchEvent(event); - } - - @Override - public boolean handleTouchEvent(MotionEvent event) { - mCentralSurfaces.onTouchEvent(event); - - // TODO(b/202981994): Move the touch debugging in this method to a central - // location. (Right now, it's split between CentralSurfaces and here.) - - // If panels aren't enabled, ignore the gesture and don't pass it down to the - // panel view. - if (!mCommandQueue.panelsEnabled()) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - Log.v( - TAG, - String.format( - "onTouchForwardedFromStatusBar: " - + "panel disabled, ignoring touch at (%d,%d)", - (int) event.getX(), - (int) event.getY() - ) - ); - } - return false; - } - - if (event.getAction() == MotionEvent.ACTION_DOWN) { - // If the view that would receive the touch is disabled, just have status - // bar eat the gesture. - if (!mView.isEnabled()) { - mShadeLog.logMotionEvent(event, - "onTouchForwardedFromStatusBar: panel view disabled"); - return true; - } - if (isFullyCollapsed() && event.getY() < 1f) { - // b/235889526 Eat events on the top edge of the phone when collapsed - mShadeLog.logMotionEvent(event, "top edge touch ignored"); - return true; - } - } - return mView.dispatchTouchEvent(event); - } - }; - public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() { return mNotificationStackScrollLayoutController; } @@ -5238,6 +5196,11 @@ public final class NotificationPanelViewController implements Dumpable { } /** */ + public boolean sendTouchEventToView(MotionEvent event) { + return mView.dispatchTouchEvent(event); + } + + /** */ public void requestLayoutOnView() { mView.requestLayout(); } @@ -5247,6 +5210,11 @@ public final class NotificationPanelViewController implements Dumpable { ViewGroupFadeHelper.reset(mView); } + /** */ + public boolean isViewEnabled() { + return mView.isEnabled(); + } + private void beginJankMonitoring() { if (mInteractionJankMonitor == null) { return; @@ -5796,11 +5764,6 @@ public final class NotificationPanelViewController implements Dumpable { mCurrentPanelState = state; } - /** Returns the handler that the status bar should forward touches to. */ - public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() { - return mStatusBarViewTouchEventHandler; - } - @VisibleForTesting StatusBarStateController getStatusBarStateController() { return mStatusBarStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 426d4fcbb27d..d000e6e75eed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -166,6 +166,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT; private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT; private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT; + private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -480,6 +481,11 @@ public class CommandQueue extends IStatusBar.Stub implements * @see IStatusBar#showRearDisplayDialog */ default void showRearDisplayDialog(int currentBaseState) {} + + /** + * @see IStatusBar#goToFullscreenFromSplit + */ + default void goToFullscreenFromSplit() {} } public CommandQueue(Context context) { @@ -1302,6 +1308,11 @@ public class CommandQueue extends IStatusBar.Stub implements .sendToTarget(); } + @Override + public void goToFullscreenFromSplit() { + mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget(); + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -1741,6 +1752,12 @@ public class CommandQueue extends IStatusBar.Stub implements for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj); } + break; + case MSG_GO_TO_FULLSCREEN_FROM_SPLIT: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).goToFullscreenFromSplit(); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index fe488a9b8336..97a47b5d1407 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -68,7 +68,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.LongRunning; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; @@ -224,12 +224,15 @@ public class NetworkControllerImpl extends BroadcastReceiver /** * Construct this controller object and register for updates. + * + * {@code @LongRunning} looper and bgExecutor instead {@code @Background} ones are used to + * address the b/246456655. This can be reverted after b/240663726 is fixed. */ @Inject public NetworkControllerImpl( Context context, - @Background Looper bgLooper, - @Background Executor bgExecutor, + @LongRunning Looper bgLooper, + @LongRunning Executor bgExecutor, SubscriptionManager subscriptionManager, CallbackHandler callbackHandler, DeviceProvisionedController deviceProvisionedController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e3336b269ca1..2d6d0a9cbeca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -983,7 +983,7 @@ public class NotificationStackScrollLayoutController { } public boolean isAddOrRemoveAnimationPending() { - return mView.isAddOrRemoveAnimationPending(); + return mView != null && mView.isAddOrRemoveAnimationPending(); } public int getVisibleNotificationCount() { @@ -1140,7 +1140,9 @@ public class NotificationStackScrollLayoutController { } public void setAlpha(float alpha) { - mView.setAlpha(alpha); + if (mView != null) { + mView.setAlpha(alpha); + } } public float calculateAppearFraction(float height) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index c6f64f3e56ba..8d06fad0f418 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -28,7 +28,6 @@ import android.os.Bundle; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.RemoteAnimationAdapter; import android.view.View; import android.view.ViewGroup; @@ -283,7 +282,11 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void animateCollapseQuickSettings(); - void onTouchEvent(MotionEvent event); + /** */ + boolean getCommandQueuePanelsEnabled(); + + /** */ + int getStatusBarWindowState(); BiometricUnlockController getBiometricUnlockController(); 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 2d48ffcf9810..31cdb0549d45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -94,7 +94,6 @@ import android.view.Display; import android.view.IRemoteAnimationRunner; import android.view.IWindowManager; import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; @@ -2031,43 +2030,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ @Override - public void onTouchEvent(MotionEvent event) { - // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's - // split between NotificationPanelViewController and here.) - if (DEBUG_GESTURES) { - if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { - EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, - event.getActionMasked(), (int) event.getX(), (int) event.getY(), - mDisabled1, mDisabled2); - } - - } - - if (SPEW) { - Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1=" - + mDisabled1 + " mDisabled2=" + mDisabled2); - } else if (CHATTY) { - if (event.getAction() != MotionEvent.ACTION_MOVE) { - Log.d(TAG, String.format( - "panel: %s at (%f, %f) mDisabled1=0x%08x mDisabled2=0x%08x", - MotionEvent.actionToString(event.getAction()), - event.getRawX(), event.getRawY(), mDisabled1, mDisabled2)); - } - } - - if (DEBUG_GESTURES) { - mGestureRec.add(event); - } + public boolean getCommandQueuePanelsEnabled() { + return mCommandQueue.panelsEnabled(); + } - if (mStatusBarWindowState == WINDOW_STATE_SHOWING) { - final boolean upOrCancel = - event.getAction() == MotionEvent.ACTION_UP || - event.getAction() == MotionEvent.ACTION_CANCEL; - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, - !upOrCancel || mShadeController.isExpandedVisible()); - } + @Override + public int getStatusBarWindowState() { + return mStatusBarWindowState; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index a6c2b2c2771c..11bc490286f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -15,14 +15,20 @@ */ package com.android.systemui.statusbar.phone +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR import android.content.res.Configuration import android.graphics.Point +import android.util.Log import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import com.android.systemui.R +import com.android.systemui.shade.ShadeController +import com.android.systemui.shade.ShadeLogger import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator +import com.android.systemui.statusbar.phone.PhoneStatusBarView.TouchEventHandler import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.UNFOLD_STATUS_BAR @@ -35,14 +41,18 @@ import java.util.Optional import javax.inject.Inject import javax.inject.Named +private const val TAG = "PhoneStatusBarViewController" + /** Controller for [PhoneStatusBarView]. */ class PhoneStatusBarViewController private constructor( view: PhoneStatusBarView, @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?, + private val centralSurfaces: CentralSurfaces, + private val shadeController: ShadeController, + private val shadeLogger: ShadeLogger, private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, private val userChipViewModel: StatusBarUserChipViewModel, private val viewUtil: ViewUtil, - touchEventHandler: PhoneStatusBarView.TouchEventHandler, private val configurationController: ConfigurationController ) : ViewController<PhoneStatusBarView>(view) { @@ -90,7 +100,7 @@ class PhoneStatusBarViewController private constructor( } init { - mView.setTouchEventHandler(touchEventHandler) + mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler()) mView.init(userChipViewModel) } @@ -120,6 +130,54 @@ class PhoneStatusBarViewController private constructor( return viewUtil.touchIsWithinView(mView, x, y) } + /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ + fun onTouchEvent(event: MotionEvent) { + if (centralSurfaces.statusBarWindowState == WINDOW_STATE_SHOWING) { + val upOrCancel = + event.action == MotionEvent.ACTION_UP || + event.action == MotionEvent.ACTION_CANCEL + centralSurfaces.setInteracting(WINDOW_STATUS_BAR, + !upOrCancel || shadeController.isExpandedVisible) + } + } + + inner class PhoneStatusBarViewTouchHandler : TouchEventHandler { + override fun onInterceptTouchEvent(event: MotionEvent) { + onTouchEvent(event) + } + + override fun handleTouchEvent(event: MotionEvent): Boolean { + onTouchEvent(event) + + // If panels aren't enabled, ignore the gesture and don't pass it down to the + // panel view. + if (!centralSurfaces.commandQueuePanelsEnabled) { + if (event.action == MotionEvent.ACTION_DOWN) { + Log.v(TAG, String.format("onTouchForwardedFromStatusBar: panel disabled, " + + "ignoring touch at (${event.x.toInt()},${event.y.toInt()})")) + } + return false + } + + if (event.action == MotionEvent.ACTION_DOWN) { + // If the view that would receive the touch is disabled, just have status + // bar eat the gesture. + if (!centralSurfaces.notificationPanelViewController.isViewEnabled) { + shadeLogger.logMotionEvent(event, + "onTouchForwardedFromStatusBar: panel view disabled") + return true + } + if (centralSurfaces.notificationPanelViewController.isFullyCollapsed && + event.y < 1f) { + // b/235889526 Eat events on the top edge of the phone when collapsed + shadeLogger.logMotionEvent(event, "top edge touch ignored") + return true + } + } + return centralSurfaces.notificationPanelViewController.sendTouchEventToView(event) + } + } + class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider { override fun getViewCenter(view: View, outPoint: Point) = when (view.id) { @@ -157,20 +215,24 @@ class PhoneStatusBarViewController private constructor( @Named(UNFOLD_STATUS_BAR) private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>, private val userChipViewModel: StatusBarUserChipViewModel, + private val centralSurfaces: CentralSurfaces, + private val shadeController: ShadeController, + private val shadeLogger: ShadeLogger, private val viewUtil: ViewUtil, private val configurationController: ConfigurationController, ) { fun create( - view: PhoneStatusBarView, - touchEventHandler: PhoneStatusBarView.TouchEventHandler + view: PhoneStatusBarView ) = PhoneStatusBarViewController( view, progressProvider.getOrNull(), + centralSurfaces, + shadeController, + shadeLogger, unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(), userChipViewModel, viewUtil, - touchEventHandler, configurationController ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index efec27099dcd..730ecded58e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -21,7 +21,6 @@ import android.view.View; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; @@ -127,11 +126,9 @@ public interface StatusBarFragmentModule { @StatusBarFragmentScope static PhoneStatusBarViewController providePhoneStatusBarViewController( PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, - @RootView PhoneStatusBarView phoneStatusBarView, - NotificationPanelViewController notificationPanelViewController) { + @RootView PhoneStatusBarView phoneStatusBarView) { return phoneStatusBarViewControllerFactory.create( - phoneStatusBarView, - notificationPanelViewController.getStatusBarTouchEventHandler()); + phoneStatusBarView); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index a9d05d11dc00..ea4020861a09 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -105,8 +105,9 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora * * This method handles inflating and attaching the view, then delegates to [updateView] to * display the correct information in the view. + * @param onViewTimeout a runnable that runs after the view timeout. */ - fun displayView(newInfo: T) { + fun displayView(newInfo: T, onViewTimeout: Runnable? = null) { val currentDisplayInfo = displayInfo // Update our list of active devices by removing it if necessary, then adding back at the @@ -173,7 +174,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora cancelViewTimeout?.run() } cancelViewTimeout = mainExecutor.executeDelayed( - { removeView(id, REMOVAL_REASON_TIMEOUT) }, + { + removeView(id, REMOVAL_REASON_TIMEOUT) + onViewTimeout?.run() + }, timeout.toLong() ) } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index ae30ca0e6a61..162c915552ed 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -22,6 +22,8 @@ import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener import android.hardware.display.DisplayManager import android.hardware.input.InputManager +import android.os.Handler +import android.os.Looper import android.os.Trace import android.view.Choreographer import android.view.Display @@ -32,6 +34,7 @@ import android.view.SurfaceControlViewHost import android.view.SurfaceSession import android.view.WindowManager import android.view.WindowlessWindowManager +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.statusbar.LightRevealEffect @@ -40,6 +43,7 @@ import com.android.systemui.statusbar.LinearLightRevealEffect import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.updates.RotationChangeProvider import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled +import com.android.systemui.util.Assert.isMainThread import com.android.systemui.util.traceSection import com.android.wm.shell.displayareahelper.DisplayAreaHelper import java.util.Optional @@ -59,6 +63,7 @@ constructor( private val displayAreaHelper: Optional<DisplayAreaHelper>, @Main private val executor: Executor, @UiBackground private val backgroundExecutor: Executor, + @Background private val bgHandler: Handler, private val rotationChangeProvider: RotationChangeProvider, ) { @@ -120,11 +125,11 @@ constructor( try { // Add the view only if we are unfolding and this is the first screen on if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) { - addView(onOverlayReady) + executeInBackground { addView(onOverlayReady) } isUnfoldHandled = true } else { // No unfold transition, immediately report that overlay is ready - ensureOverlayRemoved() + executeInBackground { ensureOverlayRemoved() } onOverlayReady.run() } } finally { @@ -139,6 +144,7 @@ constructor( return } + ensureInBackground() ensureOverlayRemoved() val newRoot = SurfaceControlViewHost(context, context.display!!, wwm) @@ -152,7 +158,7 @@ constructor( val params = getLayoutParams() newRoot.setView(newView, params) - onOverlayReady?.let { callback -> + if (onOverlayReady != null) { Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) newRoot.relayout(params) { transaction -> @@ -170,7 +176,7 @@ constructor( .setFrameTimelineVsync(vsyncId + 1) .addTransactionCommittedListener(backgroundExecutor) { Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0) - callback.run() + onOverlayReady.run() } .apply() } @@ -213,9 +219,12 @@ constructor( } private fun ensureOverlayRemoved() { - root?.release() - root = null - scrimView = null + ensureInBackground() + traceSection("ensureOverlayRemoved") { + root?.release() + root = null + scrimView = null + } } private fun getUnfoldedDisplayInfo(): DisplayInfo = @@ -228,17 +237,17 @@ constructor( private inner class TransitionListener : TransitionProgressListener { override fun onTransitionProgress(progress: Float) { - scrimView?.revealAmount = progress + executeInBackground { scrimView?.revealAmount = progress } } override fun onTransitionFinished() { - ensureOverlayRemoved() + executeInBackground { ensureOverlayRemoved() } } override fun onTransitionStarted() { // Add view for folding case (when unfolding the view is added earlier) if (scrimView == null) { - addView() + executeInBackground { addView() } } // Disable input dispatching during transition. InputManager.getInstance().cancelCurrentTouch() @@ -250,19 +259,35 @@ constructor( traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") { if (currentRotation != newRotation) { currentRotation = newRotation - scrimView?.revealEffect = createLightRevealEffect() - root?.relayout(getLayoutParams()) + executeInBackground { + scrimView?.revealEffect = createLightRevealEffect() + root?.relayout(getLayoutParams()) + } } } } } + private fun executeInBackground(f: () -> Unit) { + ensureInMainThread() + // The UiBackground executor is not used as it doesn't have a prepared looper. + bgHandler.post(f) + } + + private fun ensureInBackground() { + check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" } + } + + private fun ensureInMainThread() { + isMainThread() + } + private inner class FoldListener : FoldStateListener( context, Consumer { isFolded -> if (isFolded) { - ensureOverlayRemoved() + executeInBackground { ensureOverlayRemoved() } isUnfoldHandled = false } this.isFolded = isFolded diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 7c022eb41cd5..98d904e60603 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -431,6 +431,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0; changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth); + } else if (stream == AudioManager.STREAM_VOICE_CALL) { + final boolean routedToBluetooth = + (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL) + & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0; + changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth); } return changed; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 101dd456a004..f836463c92a2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -1769,6 +1769,7 @@ public class VolumeDialogImpl implements VolumeDialog, if (ss.level == row.requestedLevel) { row.requestedLevel = -1; } + final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL; final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY; final boolean isRingStream = row.stream == AudioManager.STREAM_RING; final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM; @@ -1813,8 +1814,12 @@ public class VolumeDialogImpl implements VolumeDialog, } else if (isRingSilent || zenMuted) { iconRes = row.iconMuteRes; } else if (ss.routedToBluetooth) { - iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute - : R.drawable.ic_volume_media_bt; + if (isVoiceCallStream) { + iconRes = R.drawable.ic_volume_bt_sco; + } else { + iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute + : R.drawable.ic_volume_media_bt; + } } else if (isStreamMuted(ss)) { iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes; } else { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 02738d5ae48b..8ef98f08c60d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -253,6 +253,12 @@ public final class WMShell implements splitScreen.onFinishedWakingUp(); } }); + mCommandQueue.addCallback(new CommandQueue.Callbacks() { + @Override + public void goToFullscreenFromSplit() { + splitScreen.goToFullscreenFromSplit(); + } + }); } @VisibleForTesting diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index afd582a3b822..fa8c8982bccb 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -87,17 +87,17 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( biometricSettingEnabledForUser = false, bouncerFullyShown = false, faceAndFpNotAuthenticated = false, + faceAuthAllowed = true, faceDisabled = false, faceLockedOut = false, - fpLockedOut = false, goingToSleep = false, keyguardAwake = false, keyguardGoingAway = false, listeningForFaceAssistant = false, occludingAppRequestingFaceAuth = false, primaryUser = false, - scanningAllowedByStrongAuth = false, secureCameraLaunched = false, + supportsDetect = true, switchingUser = false, udfpsBouncerShowing = false, udfpsFingerDown = false, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index ffd95f4041f9..d20be56d6c6b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -19,6 +19,7 @@ package com.android.keyguard import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.inputmethod.InputMethodManager +import android.widget.EditText import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils @@ -43,6 +44,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView @Mock + private lateinit var passwordEntry: EditText + @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode @@ -81,6 +84,9 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { ).thenReturn(mKeyguardMessageArea) Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea)) .thenReturn(mKeyguardMessageAreaController) + Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry) + Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry) + ).thenReturn(passwordEntry) keyguardPasswordViewController = KeyguardPasswordViewController( keyguardPasswordView, keyguardUpdateMonitor, @@ -103,7 +109,10 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true) Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) - keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() } + keyguardPasswordView.post { + verify(keyguardPasswordView).requestFocus() + verify(keyguardPasswordView).showKeyboard() + } } @Test @@ -115,6 +124,15 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { } @Test + fun testHideKeyboardWhenOnPause() { + keyguardPasswordViewController.onPause() + keyguardPasswordView.post { + verify(keyguardPasswordView).clearFocus() + verify(keyguardPasswordView).hideKeyboard() + } + } + + @Test fun startAppearAnimation() { keyguardPasswordViewController.startAppearAnimation() verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index e9aa8ee2f4d3..0e92a2904436 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -26,6 +26,7 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; @@ -100,6 +101,8 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.annotation.NonNull; + import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; @@ -267,21 +270,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // IBiometricsFace@1.0 does not support detection, only authentication. when(mFaceSensorProperties.isEmpty()).thenReturn(false); + when(mFaceSensorProperties.get(anyInt())).thenReturn( + createFaceSensorProperties(/* supportsFaceDetection = */ false)); - final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)); - componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, - "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, - "vendor/version/revision" /* softwareVersion */)); - - when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorPropertiesInternal( - 0 /* id */, - FaceSensorProperties.STRENGTH_STRONG, 1 /* maxTemplatesAllowed */, - componentInfo, FaceSensorProperties.TYPE_UNKNOWN, - false /* supportsFaceDetection */, true /* supportsSelfIllumination */, - false /* resetLockoutRequiresChallenge */)); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of( @@ -354,6 +345,28 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true); } + @NonNull + private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) { + final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); + componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, + "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, + "00000001" /* serialNumber */, "" /* softwareVersion */)); + componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, + "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, + "vendor/version/revision" /* softwareVersion */)); + + + return new FaceSensorPropertiesInternal( + 0 /* id */, + FaceSensorProperties.STRENGTH_STRONG, + 1 /* maxTemplatesAllowed */, + componentInfo, + FaceSensorProperties.TYPE_UNKNOWN, + supportsFaceDetection /* supportsFaceDetection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresChallenge */); + } + @After public void tearDown() { if (mMockitoSession != null) { @@ -611,7 +624,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() { // GIVEN unlocking with biometric is allowed - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + strongAuthNotRequired(); // THEN unlocking with face and fp is allowed Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -633,7 +646,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testUnlockingWithFaceAllowed_fingerprintLockout() { // GIVEN unlocking with biometric is allowed - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + strongAuthNotRequired(); // WHEN fingerprint is locked out fingerprintErrorLockedOut(); @@ -656,7 +669,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testUnlockingWithFpAllowed_fingerprintLockout() { // GIVEN unlocking with biometric is allowed - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + strongAuthNotRequired(); // WHEN fingerprint is locked out fingerprintErrorLockedOut(); @@ -710,10 +723,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void skipsAuthentication_whenEncryptedKeyguard() { - when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( - STRONG_AUTH_REQUIRED_AFTER_BOOT); - mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); + public void skipsAuthentication_whenStrongAuthRequired_nonBypass() { + lockscreenBypassIsNotAllowed(); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); @@ -723,15 +735,48 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void requiresAuthentication_whenEncryptedKeyguard_andBypass() { - testStrongAuthExceptOnBouncer( - STRONG_AUTH_REQUIRED_AFTER_BOOT); + public void faceDetect_whenStrongAuthRequiredAndBypass() { + // GIVEN bypass is enabled, face detection is supported and strong auth is required + lockscreenBypassIsAllowed(); + supportsFaceDetection(); + strongAuthRequiredEncrypted(); + keyguardIsVisible(); + + // WHEN the device wakes up + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + + // FACE detect is triggered, not authenticate + verify(mFaceManager).detectFace(any(), any(), anyInt()); + verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), + anyBoolean()); + + // WHEN bouncer becomes visible + setKeyguardBouncerVisibility(true); + clearInvocations(mFaceManager); + + // THEN face scanning is not run + mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); + verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), + anyBoolean()); + verify(mFaceManager, never()).detectFace(any(), any(), anyInt()); } @Test - public void requiresAuthentication_whenTimeoutKeyguard_andBypass() { - testStrongAuthExceptOnBouncer( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); + public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() { + // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required + lockscreenBypassIsAllowed(); + strongAuthRequiredEncrypted(); + keyguardIsVisible(); + + // WHEN the device wakes up + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + + // FACE detect and authenticate are NOT triggered + verify(mFaceManager, never()).detectFace(any(), any(), anyInt()); + verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), + anyBoolean()); } @Test @@ -764,24 +809,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(didFaceAuthRun).isFalse(); } - private void testStrongAuthExceptOnBouncer(int strongAuth) { - when(mKeyguardBypassController.canBypass()).thenReturn(true); - mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); - when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth); - - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - keyguardIsVisible(); - verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); - - // Stop scanning when bouncer becomes visible - setKeyguardBouncerVisibility(true); - clearInvocations(mFaceManager); - mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); - verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), - anyBoolean()); - } - @Test public void testTriesToAuthenticate_whenAssistant() { mKeyguardUpdateMonitor.setKeyguardShowing(true, true); @@ -792,10 +819,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() { - mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); - when(mKeyguardBypassController.canBypass()).thenReturn(true); + lockscreenBypassIsAllowed(); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>()); @@ -815,26 +841,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testIgnoresAuth_whenLockdown() { + public void testNoFaceAuth_whenLockDown() { + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + userDeviceLockDown(); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + keyguardIsVisible(); mTestableLooper.processAllMessages(); - when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - keyguardIsVisible(); verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); - } - - @Test - public void testTriesToAuthenticate_whenLockout() { - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); - when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT); - - keyguardIsVisible(); - verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); + verify(mFaceManager, never()).detectFace(any(), any(), anyInt()); } @Test @@ -1329,7 +1346,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED); setKeyguardBouncerVisibility(false /* isVisible */); mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - when(mKeyguardBypassController.canBypass()).thenReturn(true); + lockscreenBypassIsAllowed(); keyguardIsVisible(); // WHEN status bar state reports a change to the keyguard that would normally indicate to @@ -1513,11 +1530,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { userNotCurrentlySwitching(); // This disables face auth - when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) - .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); mTestableLooper.processAllMessages(); - assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); } @@ -1981,6 +1996,109 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ); } + @Test + public void testStrongAuthChange_lockDown_stopsFpAndFaceListeningState() { + // GIVEN device is listening for face and fingerprint + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + keyguardIsVisible(); + + verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); + verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(), + anyInt()); + + final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); + final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal); + mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; + mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel; + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN strong auth changes and device is in user lockdown + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + userDeviceLockDown(); + mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser()); + mTestableLooper.processAllMessages(); + + // THEN face and fingerprint listening are cancelled + verify(faceCancel).cancel(); + verify(callback).onBiometricRunningStateChanged( + eq(false), eq(BiometricSourceType.FACE)); + verify(fpCancel).cancel(); + verify(callback).onBiometricRunningStateChanged( + eq(false), eq(BiometricSourceType.FINGERPRINT)); + } + + @Test + public void testNonStrongBiometricAllowedChanged_stopsFaceListeningState() { + // GIVEN device is listening for face and fingerprint + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + keyguardIsVisible(); + + verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); + + final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); + mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN non-strong biometric allowed changes + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser()); + mTestableLooper.processAllMessages(); + + // THEN face and fingerprint listening are cancelled + verify(faceCancel).cancel(); + verify(callback).onBiometricRunningStateChanged( + eq(false), eq(BiometricSourceType.FACE)); + } + + @Test + public void testShouldListenForFace_withLockedDown_returnsFalse() + throws RemoteException { + keyguardNotGoingAway(); + bouncerFullyVisibleAndNotGoingToSleep(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + supportsFaceDetection(); + mTestableLooper.processAllMessages(); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); + + userDeviceLockDown(); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); + } + + private void userDeviceLockDown() { + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId)) + .thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + } + + private void supportsFaceDetection() { + when(mFaceSensorProperties.get(anyInt())) + .thenReturn(createFaceSensorProperties( + /* supportsFaceDetection = */ true)); + } + + private void lockscreenBypassIsAllowed() { + mockCanBypassLockscreen(true); + } + + private void mockCanBypassLockscreen(boolean canBypass) { + mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController); + when(mKeyguardBypassController.canBypass()).thenReturn(canBypass); + } + + private void lockscreenBypassIsNotAllowed() { + mockCanBypassLockscreen(false); + } + private void cleanupKeyguardUpdateMonitor() { if (mKeyguardUpdateMonitor != null) { mKeyguardUpdateMonitor.removeCallback(mTestCallback); @@ -2074,9 +2192,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ); } + private void strongAuthRequiredEncrypted() { + when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) + .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + } + private void strongAuthNotRequired() { when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) .thenReturn(0); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); } private void currentUserDoesNotHaveTrust() { @@ -2117,6 +2242,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { setKeyguardBouncerVisibility(true); } + private void bouncerNotVisible() { + setKeyguardBouncerVisibility(false); + } + private void setKeyguardBouncerVisibility(boolean isVisible) { mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 2b03722f9f31..ce9c1da422f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -22,6 +22,7 @@ import android.util.Log import android.util.Log.TerribleFailure import android.util.Log.TerribleFailureHandler import android.view.Choreographer.FrameCallback +import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators @@ -97,6 +98,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } @Test + @FlakyTest(bugId = 260213291) fun `starting second transition will cancel the first transition`() { runBlocking(IMMEDIATE) { val (animator, provider) = setupAnimator(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 4437394da943..311740e17310 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -265,6 +265,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() { + displayReceiverTriggered() + reset(vibratorHelper) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, @@ -278,13 +280,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText()) assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) + // Event index 1 since initially displaying the triggered chip would also log an event. + assertThat(uiEventLoggerFake.eventId(1)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id) verify(vibratorHelper, never()).vibrate(any<VibrationEffect>()) } @Test fun transferToReceiverSucceeded_nullUndoCallback_noUndo() { + displayReceiverTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, @@ -297,6 +301,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() { + displayReceiverTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, @@ -313,6 +318,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { var undoCallbackCalled = false + displayReceiverTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, @@ -325,8 +331,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { getChipbarView().getUndoButton().performClick() - // Event index 1 since initially displaying the succeeded chip would also log an event - assertThat(uiEventLoggerFake.eventId(1)) + // Event index 2 since initially displaying the triggered and succeeded chip would also log + // events. + assertThat(uiEventLoggerFake.eventId(2)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id) assertThat(undoCallbackCalled).isTrue() assertThat(getChipbarView().getChipText()) @@ -335,6 +342,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() { + displayThisDeviceTriggered() + reset(vibratorHelper) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, @@ -348,13 +357,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText()) assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) + // Event index 1 since initially displaying the triggered chip would also log an event. + assertThat(uiEventLoggerFake.eventId(1)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id) verify(vibratorHelper, never()).vibrate(any<VibrationEffect>()) } @Test fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() { + displayThisDeviceTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, @@ -367,6 +378,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() { + displayThisDeviceTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, @@ -383,6 +395,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { var undoCallbackCalled = false + displayThisDeviceTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, @@ -395,8 +408,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { getChipbarView().getUndoButton().performClick() - // Event index 1 since initially displaying the succeeded chip would also log an event - assertThat(uiEventLoggerFake.eventId(1)) + // Event index 2 since initially displaying the triggered and succeeded chip would also log + // events. + assertThat(uiEventLoggerFake.eventId(2)) .isEqualTo( MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id ) @@ -407,6 +421,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() { + displayReceiverTriggered() + reset(vibratorHelper) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, routeInfo, @@ -421,7 +437,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) - assertThat(uiEventLoggerFake.eventId(0)) + // Event index 1 since initially displaying the triggered chip would also log an event. + assertThat(uiEventLoggerFake.eventId(1)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id) verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @@ -429,6 +446,12 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null + ) + reset(vibratorHelper) + commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, routeInfo, null @@ -442,7 +465,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) - assertThat(uiEventLoggerFake.eventId(0)) + // Event index 1 since initially displaying the triggered chip would also log an event. + assertThat(uiEventLoggerFake.eventId(1)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id) verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @@ -517,6 +541,166 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { } @Test + fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() { + displayReceiverTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null + ) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() { + displayThisDeviceTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null + ) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + routeInfo, + null + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + routeInfo, + null + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, + routeInfo, + null + ) + + verify(logger).logInvalidStateTransitionError(any(), any()) + verify(windowManager, never()).addView(any(), any()) + } + + @Test fun receivesNewStateFromCommandQueue_isLogged() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, @@ -575,6 +759,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() { + displayReceiverTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, @@ -598,6 +783,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() { + displayThisDeviceTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, @@ -621,6 +807,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() { + displayReceiverTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, @@ -660,6 +847,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Test fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() { + displayThisDeviceTriggered() commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, @@ -717,6 +905,26 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { private fun ChipStateSender.getExpectedStateText(): String? { return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context) } + + // display receiver triggered state helper method to make sure we start from a valid state + // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED). + private fun displayReceiverTriggered() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null + ) + } + + // display this device triggered state helper method to make sure we start from a valid state + // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED). + private fun displayThisDeviceTriggered() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null + ) + } } private const val APP_NAME = "Fake app name" diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt index 9c36be62e46e..88651c1292c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -23,9 +23,11 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -37,6 +39,9 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { private lateinit var qsConstraint: ConstraintSet private lateinit var largeScreenConstraint: ConstraintSet + @get:Rule + val expect: Expect = Expect.create() + @Before fun setUp() { qqsConstraint = ConstraintSet().apply { @@ -344,6 +349,32 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { } @Test + fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() { + val views = mapOf( + R.id.clock to "clock", + R.id.date to "date", + R.id.statusIcons to "icons", + R.id.privacy_container to "privacy", + R.id.carrier_group to "carriers", + R.id.batteryRemainingIcon to "battery", + ) + views.forEach { (id, name) -> + expect.withMessage("$name changes height") + .that(qqsConstraint.getConstraint(id).layout.mHeight.fromConstraint()) + .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight.fromConstraint()) + expect.withMessage("$name changes width") + .that(qqsConstraint.getConstraint(id).layout.mWidth.fromConstraint()) + .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth.fromConstraint()) + } + } + + private fun Int.fromConstraint() = when (this) { + -1 -> "MATCH_PARENT" + -2 -> "WRAP_CONTENT" + else -> toString() + } + + @Test fun testEmptyCutoutDateIconsAreConstrainedWidth() { CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt index 858d0e7b94d0..1d30ad9293a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -46,7 +46,6 @@ import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT -import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider @@ -77,6 +76,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.inOrder import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @@ -212,20 +212,6 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { } @Test - fun testCorrectConstraints() { - val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java) - - verify(qqsConstraints).load(eq(context), capture(captor)) - assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header) - - verify(qsConstraints).load(eq(context), capture(captor)) - assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header) - - verify(largeScreenConstraints).load(eq(context), capture(captor)) - assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header) - } - - @Test fun testControllersCreatedAndInitialized() { verify(variableDateViewController).init() @@ -287,16 +273,6 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { } @Test - fun testLargeScreenActive_true() { - controller.largeScreenActive = false // Make sure there's a change - clearInvocations(view) - - controller.largeScreenActive = true - - verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID) - } - - @Test fun testLargeScreenActive_false() { controller.largeScreenActive = true // Make sure there's a change clearInvocations(view) @@ -696,6 +672,25 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { verify(clock).pivotY = height.toFloat() / 2 } + @Test + fun onDensityOrFontScaleChanged_reloadConstraints() { + // After density or font scale change, constraints need to be reloaded to reflect new + // dimensions. + reset(qqsConstraints) + reset(qsConstraints) + reset(largeScreenConstraints) + + configurationController.notifyDensityOrFontScaleChanged() + + val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java) + verify(qqsConstraints).load(eq(context), capture(captor)) + assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header) + verify(qsConstraints).load(eq(context), capture(captor)) + assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header) + verify(largeScreenConstraints).load(eq(context), capture(captor)) + assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header) + } + private fun View.executeLayoutChange( left: Int, top: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index b6f74f0a13ba..56a840cae267 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -530,6 +531,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); verify(mNotificationStackScrollLayoutController) .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); + verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); + reset(mKeyguardStatusViewController); } @After @@ -609,7 +612,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() { - setBottomPadding(/* stackScrollLayoutBottom= */ 100, + setBottomPadding(/* stackScrollLayoutBottom= */ 180, /* lockIconPadding= */ 20, /* indicationPadding= */ 0, /* ambientPadding= */ 0); @@ -620,7 +623,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() { - setBottomPadding(/* stackScrollLayoutBottom= */ 100, + setBottomPadding(/* stackScrollLayoutBottom= */ 180, /* lockIconPadding= */ 0, /* indicationPadding= */ 30, /* ambientPadding= */ 0); @@ -631,7 +634,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() { - setBottomPadding(/* stackScrollLayoutBottom= */ 100, + setBottomPadding(/* stackScrollLayoutBottom= */ 180, /* lockIconPadding= */ 0, /* indicationPadding= */ 0, /* ambientPadding= */ 40); @@ -802,66 +805,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { - when(mCommandQueue.panelsEnabled()).thenReturn(false); - - boolean returnVal = mNotificationPanelViewController - .getStatusBarTouchEventHandler() - .handleTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); - - assertThat(returnVal).isFalse(); - verify(mView, never()).dispatchTouchEvent(any()); - } - - @Test - public void handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() { - when(mCommandQueue.panelsEnabled()).thenReturn(true); - when(mView.isEnabled()).thenReturn(false); - - boolean returnVal = mNotificationPanelViewController - .getStatusBarTouchEventHandler() - .handleTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); - - assertThat(returnVal).isTrue(); - verify(mView, never()).dispatchTouchEvent(any()); - } - - @Test - public void handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { - when(mCommandQueue.panelsEnabled()).thenReturn(true); - when(mView.isEnabled()).thenReturn(false); - MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0); - - mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); - - verify(mView).dispatchTouchEvent(event); - } - - @Test - public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { - when(mCommandQueue.panelsEnabled()).thenReturn(true); - when(mView.isEnabled()).thenReturn(true); - MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0); - - mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); - - verify(mView).dispatchTouchEvent(event); - } - - @Test - public void handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() { - when(mCommandQueue.panelsEnabled()).thenReturn(true); - when(mView.isEnabled()).thenReturn(true); - MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0); - - mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); - - verify(mView, never()).dispatchTouchEvent(event); - } - - @Test public void testA11y_initializeNode() { AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); @@ -1014,7 +957,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView() { + public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() { givenViewAttached(); when(mResources.getBoolean( com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); @@ -1025,6 +968,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mNotificationPanelViewController.onFinishInflate(); verify(mUserSwitcherStubView, never()).inflate(); + verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt index 038af8ff5396..6155e3c16996 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.google.common.truth.Truth.assertThat import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doAnswer @@ -295,6 +296,7 @@ class ConfigurationControllerImplTest : SysuiTestCase() { } @Test + @Ignore("b/261408895") fun equivalentConfigObject_listenerNotNotified() { val config = mContext.resources.configuration val listener = createAndAddListener() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index e2843a12c51f..14d239ab48cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -27,6 +27,8 @@ import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.shade.NotificationPanelViewController +import com.android.systemui.shade.ShadeControllerImpl +import com.android.systemui.shade.ShadeLogger import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.config.UnfoldTransitionConfig @@ -41,6 +43,7 @@ import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -49,8 +52,6 @@ import java.util.Optional @SmallTest class PhoneStatusBarViewControllerTest : SysuiTestCase() { - private val touchEventHandler = TestTouchEventHandler() - @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController @Mock @@ -66,6 +67,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var userChipViewModel: StatusBarUserChipViewModel @Mock + private lateinit var centralSurfacesImpl: CentralSurfacesImpl + @Mock + private lateinit var shadeControllerImpl: ShadeControllerImpl + @Mock + private lateinit var shadeLogger: ShadeLogger + @Mock private lateinit var viewUtil: ViewUtil private lateinit var view: PhoneStatusBarView @@ -88,18 +95,6 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } @Test - fun constructor_setsTouchHandlerOnView() { - val interceptEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 10f, 10f, 0) - val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - - view.onInterceptTouchEvent(interceptEvent) - view.onTouchEvent(event) - - assertThat(touchEventHandler.lastInterceptEvent).isEqualTo(interceptEvent) - assertThat(touchEventHandler.lastEvent).isEqualTo(event) - } - - @Test fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() { val view = createViewMock() val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) @@ -115,6 +110,66 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { verify(moveFromCenterAnimation).onViewsReady(any()) } + @Test + fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { + `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false) + val returnVal = view.onTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + assertThat(returnVal).isFalse() + verify(notificationPanelViewController, never()).sendTouchEventToView(any()) + } + + @Test + fun handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() { + `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) + `when`(centralSurfacesImpl.notificationPanelViewController) + .thenReturn(notificationPanelViewController) + `when`(notificationPanelViewController.isViewEnabled).thenReturn(false) + val returnVal = view.onTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + assertThat(returnVal).isTrue() + verify(notificationPanelViewController, never()).sendTouchEventToView(any()) + } + + @Test + fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { + `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) + `when`(centralSurfacesImpl.notificationPanelViewController) + .thenReturn(notificationPanelViewController) + `when`(notificationPanelViewController.isViewEnabled).thenReturn(false) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + + view.onTouchEvent(event) + + verify(notificationPanelViewController).sendTouchEventToView(event) + } + + @Test + fun handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { + `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) + `when`(centralSurfacesImpl.notificationPanelViewController) + .thenReturn(notificationPanelViewController) + `when`(notificationPanelViewController.isViewEnabled).thenReturn(true) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0) + + view.onTouchEvent(event) + + verify(notificationPanelViewController).sendTouchEventToView(event) + } + + @Test + fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() { + `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) + `when`(centralSurfacesImpl.notificationPanelViewController) + .thenReturn(notificationPanelViewController) + `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + + view.onTouchEvent(event) + + verify(notificationPanelViewController, never()).sendTouchEventToView(any()) + } + private fun createViewMock(): PhoneStatusBarView { val view = spy(view) val viewTreeObserver = mock(ViewTreeObserver::class.java) @@ -128,9 +183,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { Optional.of(sysuiUnfoldComponent), Optional.of(progressProvider), userChipViewModel, + centralSurfacesImpl, + shadeControllerImpl, + shadeLogger, viewUtil, configurationController - ).create(view, touchEventHandler).also { + ).create(view).also { it.init() } } @@ -140,17 +198,4 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { override var isHingeAngleEnabled: Boolean = false override val halfFoldedTimeoutMillis: Int = 0 } - - private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler { - var lastEvent: MotionEvent? = null - var lastInterceptEvent: MotionEvent? = null - - override fun onInterceptTouchEvent(event: MotionEvent?) { - lastInterceptEvent = event - } - override fun handleTouchEvent(event: MotionEvent?): Boolean { - lastEvent = event - return false - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 3769f52456fb..915ea1a8cd5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -170,6 +170,34 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { } @Test + public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() { + mVolumeController.setDeviceInteractive(false); + when(mWakefullnessLifcycle.getWakefulness()).thenReturn( + WakefulnessLifecycle.WAKEFULNESS_AWAKE); + when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn( + AudioManager.DEVICE_OUT_BLE_HEADSET); + + mVolumeController.onVolumeChangedW( + AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI); + + verify(mCallback, times(1)).onStateChanged(any()); + } + + @Test + public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() { + mVolumeController.setDeviceInteractive(false); + when(mWakefullnessLifcycle.getWakefulness()).thenReturn( + WakefulnessLifecycle.WAKEFULNESS_AWAKE); + when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn( + AudioManager.DEVICE_OUT_BLUETOOTH_A2DP); + + mVolumeController.onVolumeChangedW( + AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI); + + verify(mCallback, never()).onStateChanged(any()); + } + + @Test public void testOnRemoteVolumeChanged_newStream_noNullPointer() { MediaSession.Token token = new MediaSession.Token(Process.myUid(), null); mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java new file mode 100644 index 000000000000..3767fbe98dc1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + +import android.os.Debug; +import android.util.Log; + +import org.junit.AfterClass; +import org.junit.Before; + +import java.io.File; +import java.io.IOException; + +/** + * Convenience class for grabbing a heap dump after a test class is run. + * + * To use: + * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase + * - Watch the logcat with tag MEMORY to see the path to the .ahprof file + * - adb pull /path/to/something.ahprof + * - Download ahat from https://sites.google.com/corp/google.com/ahat/home + * - java -jar ~/Downloads/ahat-1.7.2.jar something.hprof + * - Watch output for next steps + * - Profit and fix leaks! + */ +public class MemoryTrackingTestCase extends SysuiTestCase { + private static File sFilesDir = null; + private static String sLatestTestClassName = null; + + @Before public void grabFilesDir() { + if (sFilesDir == null) { + sFilesDir = mContext.getFilesDir(); + } + sLatestTestClassName = getClass().getName(); + } + + @AfterClass + public static void dumpHeap() throws IOException { + if (sFilesDir == null) { + Log.e("MEMORY", "Somehow no test cases??"); + return; + } + mockitoTearDown(); + Log.w("MEMORY", "about to dump heap"); + File path = new File(sFilesDir, sLatestTestClassName + ".ahprof"); + Debug.dumpHprofData(path.getPath()); + Log.w("MEMORY", "did it! Location: " + path); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 2e820574b435..5491379b39ac 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -290,13 +290,6 @@ public class FaceService extends SystemService { return -1; } - if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) { - // If this happens, something in KeyguardUpdateMonitor is wrong. This should only - // ever be invoked when the user is encrypted or lockdown. - Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown"); - return -1; - } - final Pair<Integer, ServiceProvider> provider = getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for detectFace"); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 1b20e6a47cb8..f9c8f064de96 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1545,7 +1545,7 @@ public final class DisplayManagerService extends SystemService { mSyncRoot.notifyAll(); } - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); Runnable work = updateDisplayStateLocked(device); if (work != null) { @@ -1564,7 +1564,7 @@ public final class DisplayManagerService extends SystemService { // We don't bother invalidating the display info caches here because any changes to the // display info will trigger a cache invalidation inside of LogicalDisplay before we hit // this point. - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); scheduleTraversalLocked(false); mPersistentDataStore.saveIfNeeded(); @@ -1593,7 +1593,7 @@ public final class DisplayManagerService extends SystemService { mDisplayStates.delete(displayId); mDisplayBrightnesses.delete(displayId); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); scheduleTraversalLocked(false); if (mDisplayWindowPolicyControllers.contains(displayId)) { @@ -1609,24 +1609,13 @@ public final class DisplayManagerService extends SystemService { } private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) { - final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); - final Runnable work = updateDisplayStateLocked(device); - if (work != null) { - mHandler.post(work); - } - final int displayId = display.getDisplayIdLocked(); + handleLogicalDisplayChangedLocked(display); + final int displayId = display.getDisplayIdLocked(); if (displayId == Display.DEFAULT_DISPLAY) { notifyDefaultDisplayDeviceUpdated(display); } - - DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); - if (dpc != null) { - dpc.onDisplayChanged(); - } - mPersistentDataStore.saveIfNeeded(); mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS); - handleLogicalDisplayChangedLocked(display); } private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) { @@ -1638,7 +1627,7 @@ public final class DisplayManagerService extends SystemService { final int displayId = display.getDisplayIdLocked(); final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { - dpc.onDeviceStateTransition(); + dpc.onDisplayChanged(); } } @@ -2348,9 +2337,13 @@ public final class DisplayManagerService extends SystemService { } } - private void sendDisplayEventLocked(int displayId, @DisplayEvent int event) { - Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); - mHandler.sendMessage(msg); + private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) { + // Only send updates outside of DisplayManagerService for enabled displays + if (display.isEnabledLocked()) { + int displayId = display.getDisplayIdLocked(); + Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + mHandler.sendMessage(msg); + } } private void sendDisplayGroupEvent(int groupId, int event) { @@ -2636,8 +2629,7 @@ public final class DisplayManagerService extends SystemService { } private void handleBrightnessChange(LogicalDisplay display) { - sendDisplayEventLocked(display.getDisplayIdLocked(), - DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); } private DisplayDevice getDeviceForDisplayLocked(int displayId) { @@ -2854,12 +2846,12 @@ public final class DisplayManagerService extends SystemService { * Returns the list of all display ids. */ @Override // Binder call - public int[] getDisplayIds() { + public int[] getDisplayIds(boolean includeDisabled) { final int callingUid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { - return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid); + return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid, includeDisabled); } } finally { Binder.restoreCallingIdentity(token); @@ -3337,6 +3329,11 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked( + displayId, /* includeDisabled= */ false); + if (display == null || !display.isEnabledLocked()) { + return null; + } DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { return dpc.getBrightnessInfo(); diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index c131ed62c6b2..ecae8330d532 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; import static android.os.PowerManager.BRIGHTNESS_INVALID; @@ -1457,7 +1458,7 @@ public class DisplayModeDirector { SparseArray<Display.Mode[]> modes = new SparseArray<>(); SparseArray<Display.Mode> defaultModes = new SparseArray<>(); DisplayInfo info = new DisplayInfo(); - Display[] displays = dm.getDisplays(); + Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); for (Display d : displays) { final int displayId = d.getDisplayId(); d.getDisplayInfo(info); @@ -2332,7 +2333,8 @@ public class DisplayModeDirector { sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); synchronized (mSensorObserverLock) { - for (Display d : mDisplayManager.getDisplays()) { + for (Display d : mDisplayManager.getDisplays( + DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) { mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); } } @@ -2343,7 +2345,8 @@ public class DisplayModeDirector { } private void recalculateVotesLocked() { - final Display[] displays = mDisplayManager.getDisplays(); + final Display[] displays = mDisplayManager.getDisplays( + DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); for (Display d : displays) { int displayId = d.getDisplayId(); Vote vote = null; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index c426e69332c7..d7bbb109b6f7 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -491,6 +491,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final String mSuspendBlockerIdProxNegative; private final String mSuspendBlockerIdProxDebounce; + private boolean mIsEnabled; + private boolean mIsInTransition; + /** * Creates the display power controller. */ @@ -512,6 +515,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); mDisplayStatsId = mUniqueDisplayId.hashCode(); + mIsEnabled = logicalDisplay.isEnabledLocked(); + mIsInTransition = logicalDisplay.isInTransitionLocked(); mHandler = new DisplayControllerHandler(handler.getLooper()); mLastBrightnessEvent = new BrightnessEvent(mDisplayId); mTempBrightnessEvent = new BrightnessEvent(mDisplayId); @@ -789,13 +794,30 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDeviceConfig config = device.getDisplayDeviceConfig(); final IBinder token = device.getDisplayTokenLocked(); final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + final boolean isEnabled = mLogicalDisplay.isEnabledLocked(); + final boolean isInTransition = mLogicalDisplay.isInTransitionLocked(); mHandler.post(() -> { + boolean changed = false; if (mDisplayDevice != device) { + changed = true; mDisplayDevice = device; mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; loadFromDisplayDeviceConfig(token, info); + + // Since the underlying display-device changed, we really don't know the + // last command that was sent to change it's state. Lets assume it is unknown so + // that we trigger a change immediately. + mPowerState.resetScreenState(); + } + if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { + changed = true; + mIsEnabled = isEnabled; + mIsInTransition = isInTransition; + } + + if (changed) { if (DEBUG) { Trace.beginAsyncSection("DisplayPowerController#updatePowerState", 0); } @@ -808,15 +830,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } /** - * Called when the displays are preparing to transition from one device state to another. - * This process involves turning off some displays so we need updatePowerState() to run and - * calculate the new state. - */ - public void onDeviceStateTransition() { - sendUpdatePowerState(); - } - - /** * Unregisters all listeners and interrupts all running threads; halting future work. * * This method should be called when the DisplayPowerController is no longer in use; i.e. when @@ -1291,8 +1304,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mIgnoreProximityUntilChanged = false; } - if (!mLogicalDisplay.isEnabled() - || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + if (!mIsEnabled + || mIsInTransition || mScreenOffBecauseOfProximity) { state = Display.STATE_OFF; } diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 2f22d33f552a..f650b118b815 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -145,7 +145,7 @@ final class DisplayPowerState { public void setScreenState(int state) { if (mScreenState != state) { if (DEBUG) { - Slog.d(TAG, "setScreenState: state=" + state); + Slog.w(TAG, "setScreenState: state=" + Display.stateToString(state)); } mScreenState = state; @@ -339,6 +339,15 @@ final class DisplayPowerState { if (mColorFade != null) mColorFade.dump(pw); } + /** + * Resets the screen state to unknown. Useful when the underlying display-device changes for the + * LogicalDisplay and we do not know the last state that was sent to it. + */ + void resetScreenState() { + mScreenState = Display.STATE_UNKNOWN; + mScreenReady = false; + } + private void scheduleScreenUpdate() { if (!mScreenUpdatePending) { mScreenUpdatePending = true; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index d14902eaf8f5..e6f27c1b0dd9 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -18,7 +18,6 @@ package com.android.server.display; import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Point; @@ -68,33 +67,6 @@ import java.util.Objects; final class LogicalDisplay { private static final String TAG = "LogicalDisplay"; - /** - * Phase indicating the logical display's existence is hidden from the rest of the framework. - * This can happen if the current layout has specifically requested to keep this display - * disabled. - */ - static final int DISPLAY_PHASE_DISABLED = -1; - - /** - * Phase indicating that the logical display is going through a layout transition. - * When in this phase, other systems can choose to special case power-state handling of a - * display that might be in a transition. - */ - static final int DISPLAY_PHASE_LAYOUT_TRANSITION = 0; - - /** - * The display is exposed to the rest of the system and its power state is determined by a - * power-request from PowerManager. - */ - static final int DISPLAY_PHASE_ENABLED = 1; - - @IntDef(prefix = {"DISPLAY_PHASE" }, value = { - DISPLAY_PHASE_DISABLED, - DISPLAY_PHASE_LAYOUT_TRANSITION, - DISPLAY_PHASE_ENABLED - }) - @interface DisplayPhase {} - // The layer stack we use when the display has been blanked to prevent any // of its content from appearing. private static final int BLANK_LAYER_STACK = -1; @@ -159,14 +131,6 @@ final class LogicalDisplay { private final Rect mTempDisplayRect = new Rect(); /** - * Indicates the current phase of the display. Generally, phases supersede any - * requests from PowerManager in DPC's calculation for the display state. Only when the - * phase is ENABLED does PowerManager's request for the display take effect. - */ - @DisplayPhase - private int mPhase = DISPLAY_PHASE_ENABLED; - - /** * The UID mappings for refresh rate override */ private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides; @@ -181,12 +145,22 @@ final class LogicalDisplay { */ private final SparseArray<Float> mTempFrameRateOverride; + // Indicates the display is enabled (allowed to be ON). + private boolean mIsEnabled; + + // Indicates the display is part of a transition from one device-state ({@link + // DeviceStateManager}) to another. Being a "part" of a transition means that either + // the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing. + private boolean mIsInTransition; + public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; mPendingFrameRateOverrideUids = new ArraySet<>(); mTempFrameRateOverride = new SparseArray<>(); + mIsEnabled = true; + mIsInTransition = false; } /** @@ -525,7 +499,7 @@ final class LogicalDisplay { // Prevent displays that are disabled from receiving input. // TODO(b/188914255): Remove once input can dispatch against device vs layerstack. device.setDisplayFlagsLocked(t, - (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE) + (isEnabledLocked() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE) ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0); @@ -767,32 +741,45 @@ final class LogicalDisplay { return old; } - public void setPhase(@DisplayPhase int phase) { - mPhase = phase; + /** + * @return {@code true} if the LogicalDisplay is enabled or {@code false} + * if disabled indicating that the display should be hidden from the rest of the apps and + * framework. + */ + public boolean isEnabledLocked() { + return mIsEnabled; + } + + /** + * Sets the display as enabled. + * + * @param enable True if enabled, false otherwise. + */ + public void setEnabledLocked(boolean enabled) { + mIsEnabled = enabled; } /** - * Returns the currently set phase for this LogicalDisplay. Phases are used when transitioning - * from one device state to another. {@see LogicalDisplayMapper}. + * @return {@code true} if the LogicalDisplay is in a transition phase. This is used to indicate + * that we are getting ready to swap the underlying display-device and the display should be + * rendered appropriately to reduce jank. */ - @DisplayPhase - public int getPhase() { - return mPhase; + public boolean isInTransitionLocked() { + return mIsInTransition; } /** - * @return {@code true} if the LogicalDisplay is enabled or {@code false} - * if disabled indicating that the display should be hidden from the rest of the apps and - * framework. + * Sets the transition phase. + * @param isInTransition True if it display is in transition. */ - public boolean isEnabled() { - // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase. - return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION; + public void setIsInTransitionLocked(boolean isInTransition) { + mIsInTransition = isInTransition; } public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); - pw.println("mPhase=" + mPhase); + pw.println("mIsEnabled=" + mIsEnabled); + pw.println("mIsInTransition=" + mIsInTransition); pw.println("mLayerStack=" + mLayerStack); pw.println("mHasContent=" + mHasContent); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 70c9e23c6af8..778e41820433 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -39,7 +39,6 @@ import android.view.DisplayAddress; import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.display.LogicalDisplay.DisplayPhase; import com.android.server.display.layout.Layout; import java.io.PrintWriter; @@ -167,6 +166,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { + this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap()); + } + + LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, + @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, + @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) { mSyncRoot = syncRoot; mPowerManager = context.getSystemService(PowerManager.class); mInteractive = mPowerManager.isInteractive(); @@ -181,7 +186,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray( com.android.internal.R.array.config_deviceStatesOnWhichToSleep)); mDisplayDeviceRepo.addListener(this); - mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); + mDeviceStateToLayoutMap = deviceStateToLayoutMap; } @Override @@ -218,10 +223,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } public LogicalDisplay getDisplayLocked(int displayId) { - return mLogicalDisplays.get(displayId); + return getDisplayLocked(displayId, /* includeDisabled= */ true); + } + + public LogicalDisplay getDisplayLocked(int displayId, boolean includeDisabled) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display == null || display.isEnabledLocked() || includeDisabled) { + return display; + } + return null; } public LogicalDisplay getDisplayLocked(DisplayDevice device) { + return getDisplayLocked(device, /* includeDisabled= */ true); + } + + public LogicalDisplay getDisplayLocked(DisplayDevice device, boolean includeDisabled) { if (device == null) { return null; } @@ -229,21 +246,26 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { for (int i = 0; i < count; i++) { final LogicalDisplay display = mLogicalDisplays.valueAt(i); if (display.getPrimaryDisplayDeviceLocked() == device) { - return display; + if (display.isEnabledLocked() || includeDisabled) { + return display; + } + return null; } } return null; } - public int[] getDisplayIdsLocked(int callingUid) { + public int[] getDisplayIdsLocked(int callingUid, boolean includeDisabled) { final int count = mLogicalDisplays.size(); int[] displayIds = new int[count]; int n = 0; for (int i = 0; i < count; i++) { LogicalDisplay display = mLogicalDisplays.valueAt(i); - DisplayInfo info = display.getDisplayInfoLocked(); - if (info.hasAccess(callingUid)) { - displayIds[n++] = mLogicalDisplays.keyAt(i); + if (display.isEnabledLocked() || includeDisabled) { + DisplayInfo info = display.getDisplayInfoLocked(); + if (info.hasAccess(callingUid)) { + displayIds[n++] = mLogicalDisplays.keyAt(i); + } } } if (n != count) { @@ -364,14 +386,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { void setDeviceStateLocked(int state, boolean isOverrideActive) { Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState - + ", interactive=" + mInteractive); + + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted); // As part of a state transition, we may need to turn off some displays temporarily so that // the transition is smooth. Plus, on some devices, only one internal displays can be - // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be + // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be // temporarily turned off. - if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) { - resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION); - } + resetLayoutLocked(mDeviceState, state, /* isStateChangeStarting= */ true); mPendingDeviceState = state; final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); @@ -481,7 +501,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { final LogicalDisplay display = mLogicalDisplays.valueAt(i); - if (display.getPhase() != LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) { + if (!display.isInTransitionLocked()) { continue; } @@ -497,7 +517,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } private void transitionToPendingStateLocked() { - resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED); + resetLayoutLocked(mDeviceState, mPendingDeviceState, /* isStateChangeStarting= */ false); mDeviceState = mPendingDeviceState; mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; applyLayoutLocked(); @@ -789,17 +809,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { /** * Goes through all the displays used in the layouts for the specified {@code fromState} and - * {@code toState} and applies the specified {@code phase}. When a new layout is requested, we - * put the displays that will change into a transitional phase so that they can all be turned - * OFF. Once all are confirmed OFF, then this method gets called again to reset the phase to - * normal operation. This helps to ensure that all display-OFF requests are made before + * {@code toState} and un/marks them for transition. When a new layout is requested, we + * mark the displays that will change into a transitional phase so that they can all be turned + * OFF. Once all are confirmed OFF, then this method gets called again to reset transition + * marker. This helps to ensure that all display-OFF requests are made before * display-ON which in turn hides any resizing-jank windows might incur when switching displays. * * @param fromState The state we are switching from. * @param toState The state we are switching to. - * @param phase The new phase to apply to the displays. + * @param isStateChangeStarting Indicates whether to start or end Transition phase. */ - private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) { + private void resetLayoutLocked(int fromState, int toState, boolean isStateChangeStarting) { final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState); final Layout toLayout = mDeviceStateToLayoutMap.get(toState); @@ -817,12 +837,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // new layout. final DisplayAddress address = device.getDisplayDeviceInfoLocked().address; - // Virtual displays do not have addresses. + // Virtual displays do not have addresses, so account for nulls. final Layout.Display fromDisplay = address != null ? fromLayout.getByAddress(address) : null; final Layout.Display toDisplay = address != null ? toLayout.getByAddress(address) : null; + // If the display is in one of the layouts but not the other, then the content will + // change, so in this case we also want to blank the displays to avoid jank. + final boolean displayNotInBothLayouts = (fromDisplay == null) != (toDisplay == null); + // If a layout doesn't mention a display-device at all, then the display-device defaults // to enabled. This is why we treat null as "enabled" in the code below. final boolean wasEnabled = fromDisplay == null || fromDisplay.isEnabled(); @@ -837,16 +861,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // 3) It's enabled, but it's mapped to a new logical display ID. To the user this // would look like apps moving from one screen to another since task-stacks stay // with the logical display [ID]. + // 4) It's in one layout but not the other, so the content will change. final boolean isTransitioning = - (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) + logicalDisplay.isInTransitionLocked() || (wasEnabled != willBeEnabled) - || deviceHasNewLogicalDisplayId; + || deviceHasNewLogicalDisplayId + || displayNotInBothLayouts; if (isTransitioning) { - setDisplayPhase(logicalDisplay, phase); - if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) { - mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); + if (isStateChangeStarting != logicalDisplay.isInTransitionLocked()) { + Slog.i(TAG, "Set isInTransition on display " + displayId + ": " + + isStateChangeStarting); } + // This will either mark the display as "transitioning" if we are starting to change + // the device state, or remove the transitioning marker if the state change is + // ending. + logicalDisplay.setIsInTransitionLocked(isStateChangeStarting); + mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); } } } @@ -891,9 +922,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { newDisplay.swapDisplaysLocked(oldDisplay); } - if (!displayLayout.isEnabled()) { - setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED); - } + setEnabledLocked(newDisplay, displayLayout.isEnabled()); } } @@ -912,23 +941,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); display.updateLocked(mDisplayDeviceRepo); mLogicalDisplays.put(displayId, display); - setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED); return display; } - private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) { + private void setEnabledLocked(LogicalDisplay display, boolean isEnabled) { final int displayId = display.getDisplayIdLocked(); final DisplayInfo info = display.getDisplayInfoLocked(); final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode && (info.type != Display.TYPE_INTERNAL); - if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) { + if (isEnabled && disallowSecondaryDisplay) { Slog.i(TAG, "Not creating a logical display for a secondary display because single" + " display demo mode is enabled: " + display.getDisplayInfoLocked()); - phase = LogicalDisplay.DISPLAY_PHASE_DISABLED; + isEnabled = false; } - display.setPhase(phase); + if (display.isEnabledLocked() != isEnabled) { + Slog.i(TAG, "SetEnabled on display " + displayId + ": " + isEnabled); + display.setEnabledLocked(isEnabled); + } } private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 3443d455ee82..15ba760dbca3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -138,6 +138,11 @@ public class PackageManagerServiceUtils { public final static Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG = pkgSetting -> pkgSetting.getPkg() == null; + // This is a horrible hack to workaround b/240373119, specifically for fixing the T branch. + // A proper fix should be implemented in master instead. + public static final ThreadLocal<Boolean> DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = + ThreadLocal.withInitial(() -> false); + /** * Components of apps targeting Android T and above will stop receiving intents from * external callers that do not match its declared intent filters. @@ -1089,6 +1094,8 @@ public class PackageManagerServiceUtils { PlatformCompat compat, ComponentResolverApi resolver, List<ResolveInfo> resolveInfos, boolean isReceiver, Intent intent, String resolvedType, int filterCallingUid) { + if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return; + final Printer logPrinter = DEBUG_INTENT_MATCHING ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM) : null; @@ -1122,7 +1129,7 @@ public class PackageManagerServiceUtils { throw new IllegalArgumentException("Unsupported component type"); } - if (comp.getIntents().isEmpty()) { + if (comp == null || comp.getIntents().isEmpty()) { continue; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 352d4be6c7ce..a4c9684af418 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2864,6 +2864,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { return key_consumed; } break; + case KeyEvent.KEYCODE_DPAD_UP: + if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); + if (statusbar != null) { + statusbar.goToFullscreenFromSplit(); + } + return key_consumed; + } + break; case KeyEvent.KEYCODE_SLASH: if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) { toggleKeyboardShortcutsMenu(event.getDeviceId()); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 97a57e066fc7..3baaa9d44019 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -202,6 +202,9 @@ public class KeyguardServiceDelegate { if (!mKeyguardState.enabled) { mKeyguardService.setKeyguardEnabled(mKeyguardState.enabled); } + if (mKeyguardState.dreaming) { + mKeyguardService.onDreamingStarted(); + } } @Override diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index c758f487d4de..9957140162a0 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -179,4 +179,9 @@ public interface StatusBarManagerInternal { * @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog */ void showRearDisplayDialog(int currentBaseState); + + /** + * Called when requested to go to fullscreen from the active split app. + */ + void goToFullscreenFromSplit(); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 194dfb25583f..5a91dc6ef8d9 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -696,6 +696,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } catch (RemoteException ex) { } } } + + @Override + public void goToFullscreenFromSplit() { + if (mBar != null) { + try { + mBar.goToFullscreenFromSplit(); + } catch (RemoteException ex) { } + } + } }; private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 6eaeb15e30b5..565b8f8d117c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -146,6 +146,7 @@ import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.am.HostingRecord; import com.android.server.am.UserState; +import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.utils.Slogf; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; @@ -2634,12 +2635,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // ActivityStarter will acquire the lock where the places need, so execute the request // outside of the lock. try { + // We need to temporarily disable the explicit intent filter matching enforcement + // because Task does not store the resolved type of the intent data, causing filter + // mismatch in certain cases. (b/240373119) + PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(true); return mService.getActivityStartController().startActivityInPackage(taskCallingUid, callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null, null, 0, 0, options, userId, task, "startActivityFromRecents", false /* validateIncomingUser */, null /* originatingPendingIntent */, false /* allowBackgroundActivityStart */); } finally { + PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false); synchronized (mService.mGlobalLock) { mService.continueWindowLayout(); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 2c289c99d3da..0bb4022d9289 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -63,7 +63,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManager.TRANSIT_WAKE; import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED; import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM; @@ -804,13 +803,7 @@ public class DisplayPolicy { if (!mDisplayContent.isDefaultDisplay) { return; } - if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled() - && !mDisplayContent.mTransitionController.isCollecting()) { - // Start a transition for waking. This is needed for showWhenLocked activities. - mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE, - 0 /* flags */, null /* trigger */, mDisplayContent); - } - mService.mAtmService.mKeyguardController.updateDeferWakeTransition( + mService.mAtmService.mKeyguardController.updateDeferTransitionForAod( mAwake /* waiting */); } } diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index d76f6be93aeb..e6a0f4d5d77d 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -176,7 +176,7 @@ class KeyguardController { final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing) || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved); if (aodRemoved) { - updateDeferWakeTransition(false /* waiting */); + updateDeferTransitionForAod(false /* waiting */); } if (!keyguardChanged && !aodChanged) { setWakeTransitionReady(); @@ -535,24 +535,25 @@ class KeyguardController { private final Runnable mResetWaitTransition = () -> { synchronized (mWindowManager.mGlobalLock) { - updateDeferWakeTransition(false /* waiting */); + updateDeferTransitionForAod(false /* waiting */); } }; - void updateDeferWakeTransition(boolean waiting) { + // Defer transition until AOD dismissed. + void updateDeferTransitionForAod(boolean waiting) { if (waiting == mWaitingForWakeTransition) { return; } - if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) { + if (!mService.getTransitionController().isCollecting()) { return; } - // if aod is showing, defer the wake transition until aod state changed. + // if AOD is showing, defer the wake transition until AOD state changed. if (waiting && isAodShowing(DEFAULT_DISPLAY)) { mWaitingForWakeTransition = true; mWindowManager.mAtmService.getTransitionController().deferTransitionReady(); mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS); } else if (!waiting) { - // dismiss the deferring if the aod state change or cancel awake. + // dismiss the deferring if the AOD state change or cancel awake. mWaitingForWakeTransition = false; mWindowManager.mAtmService.getTransitionController().continueTransitionReady(); mWindowManager.mH.removeCallbacks(mResetWaitTransition); @@ -659,10 +660,18 @@ class KeyguardController { mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false); } + boolean hasChange = false; if (lastOccluded != mOccluded) { controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity); + hasChange = true; } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) { controller.handleKeyguardGoingAwayChanged(display); + hasChange = true; + } + // Collect the participates for shell transition, so that transition won't happen too + // early since the transition was set ready. + if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) { + display.mTransitionController.collect(top); } } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 6edb63c14c64..76ee733cbb27 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -674,6 +674,12 @@ final class LetterboxUiController { + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration())); pw.println(prefix + " letterboxVerticalPositionMultiplier=" + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration())); + pw.println(prefix + " letterboxPositionForHorizontalReachability=" + + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString( + mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())); + pw.println(prefix + " letterboxPositionForVerticalReachability=" + + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString( + mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())); pw.println(prefix + " fixedOrientationLetterboxAspectRatio=" + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); pw.println(prefix + " defaultMinAspectRatioForUnresizableApps=" diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index c6989ef906d5..d619547dbbd1 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -691,6 +691,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (mainWindow == null || mainWindow.mRemoved) { removalInfo.playRevealAnimation = false; } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) { + removalInfo.roundedCornerRadius = + topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow); removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow); removalInfo.mainFrame = mainWindow.getRelativeFrame(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index c22091b4eacb..b8cf0ad2e774 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -1206,6 +1206,12 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println("Default position for vertical reachability: " + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString( mLetterboxConfiguration.getDefaultPositionForVerticalReachability())); + pw.println("Current position for horizontal reachability:" + + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString( + mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())); + pw.println("Current position for vertical reachability:" + + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString( + mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())); pw.println("Is education enabled: " + mLetterboxConfiguration.getIsEducationEnabled()); pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: " diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 1e97c1c5c5bc..2edb909258f9 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -287,7 +287,7 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); - final int displayIds[] = bs.getDisplayIds(); + final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true); final int size = displayIds.length; assertTrue(size > 0); @@ -1174,7 +1174,8 @@ public class DisplayManagerServiceTest { DisplayManagerService.BinderService displayManagerBinderService, FakeDisplayDevice displayDevice) { - final int[] displayIds = displayManagerBinderService.getDisplayIds(); + final int[] displayIds = displayManagerBinderService.getDisplayIds( + /* includeDisabled= */ true); assertTrue(displayIds.length > 0); int displayId = Display.INVALID_DISPLAY; for (int i = 0; i < displayIds.length; i++) { diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index cc68ba88f76e..d515fae4afe2 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -30,6 +30,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -53,6 +55,8 @@ import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.display.layout.Layout; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,6 +64,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.InputStream; import java.io.OutputStream; @@ -85,6 +90,7 @@ public class LogicalDisplayMapperTest { @Mock Resources mResourcesMock; @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; + @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap(); @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; @@ -134,7 +140,8 @@ public class LogicalDisplayMapperTest { mLooper = new TestLooper(); mHandler = new Handler(mLooper.getLooper()); mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo, - mListenerMock, new DisplayManagerService.SyncRoot(), mHandler); + mListenerMock, new DisplayManagerService.SyncRoot(), mHandler, + mDeviceStateToLayoutMapSpy); } @@ -261,7 +268,8 @@ public class LogicalDisplayMapperTest { add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0)); add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); - int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID); + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, + /* includeDisabled= */ true); assertEquals(3, ids.length); Arrays.sort(ids); assertEquals(DEFAULT_DISPLAY, ids[0]); @@ -413,6 +421,178 @@ public class LogicalDisplayMapperTest { /* isBootCompleted= */true)); } + @Test + public void testDeviceStateLocked() { + DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + + Layout layout = new Layout(); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false); + when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); + + layout = new Layout(); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true); + when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout); + when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout); + + LogicalDisplay display1 = add(device1); + assertEquals(info(display1).address, info(device1).address); + assertEquals(DEFAULT_DISPLAY, id(display1)); + + LogicalDisplay display2 = add(device2); + assertEquals(info(display2).address, info(device2).address); + // We can only have one default display + assertEquals(DEFAULT_DISPLAY, id(display1)); + + mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(2, false); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + } + + @Test + public void testEnabledAndDisabledDisplays() { + DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress(); + DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress(); + DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress(); + + TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, Display.TYPE_INTERNAL, + 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, Display.TYPE_INTERNAL, + 200, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, Display.TYPE_INTERNAL, + 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + + Layout threeDevicesEnabledLayout = new Layout(); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressOne, + /* isDefault= */ true, + /* isEnabled= */ true); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressTwo, + /* isDefault= */ false, + /* isEnabled= */ true); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressThree, + /* isDefault= */ false, + /* isEnabled= */ true); + + when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT)) + .thenReturn(threeDevicesEnabledLayout); + + LogicalDisplay display1 = add(device1); + LogicalDisplay display2 = add(device2); + LogicalDisplay display3 = add(device3); + + // ensure 3 displays are returned + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false); + assertEquals(3, ids.length); + Arrays.sort(ids); + assertEquals(DEFAULT_DISPLAY, ids[0]); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(), + /* includeDisabled= */ false)); + + Layout oneDeviceEnabledLayout = new Layout(); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressOne, + /* isDefault= */ true, + /* isEnabled= */ true); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressTwo, + /* isDefault= */ false, + /* isEnabled= */ false); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressThree, + /* isDefault= */ false, + /* isEnabled= */ false); + + when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout); + when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout); + + // 1) Set the new state + // 2) Mark the displays as STATE_OFF so that it can continue with transition + // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state + // 4) Dispatch handler events. + mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked( + Process.SYSTEM_UID, false); + if (allDisplayIds.length != 1) { + throw new RuntimeException("Displays: \n" + + mLogicalDisplayMapper.getDisplayLocked(device1).toString() + + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString() + + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString()); + } + // ensure only one display is returned + assertEquals(1, allDisplayIds.length); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1, + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked(device2, + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked(device3, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(), + /* includeDisabled= */ false)); + + // Now do it again to go back to state 1 + mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked( + Process.SYSTEM_UID, false); + + // ensure all three displays are returned + assertEquals(3, threeDisplaysEnabled.length); + } + ///////////////// // Helper Methods ///////////////// @@ -477,6 +657,7 @@ public class LogicalDisplayMapperTest { class TestDisplayDevice extends DisplayDevice { private DisplayDeviceInfo mInfo; private DisplayDeviceInfo mSentInfo; + private int mState; TestDisplayDevice() { super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index b0738fdb78d0..50d2a51b4bbe 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -128,12 +128,12 @@ public class LogicalDisplayTest { verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); reset(t); - mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED); + mLogicalDisplay.setEnabledLocked(false); mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); verify(t).setDisplayFlags(any(), eq(0)); reset(t); - mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED); + mLogicalDisplay.setEnabledLocked(true); mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java index ce322f7cb6e6..8bd6fcdbb9c2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java @@ -38,6 +38,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; @@ -47,7 +48,6 @@ import android.permission.PermissionManager; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; -import com.android.activitycontext.ActivityContext; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; import com.android.server.LocalServices; @@ -611,34 +611,23 @@ public class CrossProfileAppsServiceImplTest { mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER); Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); - IBinder result = ActivityContext.getWithContext(activity -> { - try { - IBinder targetTask = activity.getActivityToken(); - mCrossProfileAppsServiceImpl.startActivityAsUser( - mIApplicationThread, - PACKAGE_ONE, - FEATURE_ID, - ACTIVITY_COMPONENT, - UserHandle.of(PRIMARY_USER).getIdentifier(), - true, - targetTask, - options); - return targetTask; - } catch (Exception re) { - return null; - } - }); - if (result == null) { - throw new Exception(); - } - + Binder targetTask = new Binder(); + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_ONE, + FEATURE_ID, + ACTIVITY_COMPONENT, + UserHandle.of(PRIMARY_USER).getIdentifier(), + true, + targetTask, + options); verify(mActivityTaskManagerInternal) .startActivityAsUser( nullable(IApplicationThread.class), eq(PACKAGE_ONE), eq(FEATURE_ID), any(Intent.class), - eq(result), + eq(targetTask), anyInt(), eq(options), eq(PRIMARY_USER)); |