Merge "Enable configuring multiple loggable dream prefixes." into tm-qpr-dev
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index b09463e..3bf3067 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -449,7 +449,8 @@
&& isVisible == that.isVisible
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
- && parentTaskId == that.parentTaskId;
+ && parentTaskId == that.parentTaskId
+ && Objects.equals(topActivity, that.topActivity);
}
/**
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index a432b8d..fd94969 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -42,12 +42,15 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.os.BackgroundThread;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -63,6 +66,7 @@
@RequiresFeature(PackageManager.FEATURE_APP_WIDGETS)
public class AppWidgetManager {
+
/**
* Activity action to launch from your {@link AppWidgetHost} activity when you want to
* pick an AppWidget to display. The AppWidget picker activity will be launched.
@@ -332,6 +336,17 @@
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
/**
+ * A combination broadcast of APPWIDGET_ENABLED and APPWIDGET_UPDATE.
+ * Sent during boot time and when the host is binding the widget for the very first time
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_APPWIDGET_ENABLE_AND_UPDATE = "android.appwidget.action"
+ + ".APPWIDGET_ENABLE_AND_UPDATE";
+
+ /**
* Sent when the custom extras for an AppWidget change.
*
* <p class="note">This is a protected intent that can only be sent
@@ -456,6 +471,8 @@
public static final String ACTION_APPWIDGET_HOST_RESTORED
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
+ private static final String TAG = "AppWidgetManager";
+
/**
* An intent extra that contains multiple appWidgetIds. These are id values as
* they were provided to the application during a recent restore from backup. It is
@@ -511,6 +528,26 @@
mPackageName = context.getOpPackageName();
mService = service;
mDisplayMetrics = context.getResources().getDisplayMetrics();
+ if (mService == null) {
+ return;
+ }
+ BackgroundThread.getExecutor().execute(() -> {
+ try {
+ mService.notifyProviderInheritance(getInstalledProvidersForPackage(mPackageName,
+ null)
+ .stream().filter(Objects::nonNull)
+ .map(info -> info.provider).filter(p -> {
+ try {
+ Class clazz = Class.forName(p.getClassName());
+ return AppWidgetProvider.class.isAssignableFrom(clazz);
+ } catch (Exception e) {
+ return false;
+ }
+ }).toArray(ComponentName[]::new));
+ } catch (Exception e) {
+ Log.e(TAG, "Nofity service of inheritance info", e);
+ }
+ });
}
/**
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index a5d2198..3344ebc 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -58,7 +58,12 @@
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
- if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ if (AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE.equals(action)) {
+ this.onReceive(context, new Intent(intent)
+ .setAction(AppWidgetManager.ACTION_APPWIDGET_ENABLED));
+ this.onReceive(context, new Intent(intent)
+ .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
+ } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 0fd164d..085bfca 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -918,7 +918,6 @@
}
}
-
/**
* Forwards BiometricStateListener to FingerprintService
* @param listener new BiometricStateListener being added
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c9fd129..a955dbb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9793,6 +9793,13 @@
"fingerprint_side_fps_auth_downtime";
/**
+ * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * @hide
+ */
+ public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+ "sfps_require_screen_on_to_auth_enabled";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6c689ff..44997b4 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -578,6 +578,11 @@
*/
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
+ /**
+ * (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE
+ */
+ public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f7467b5..7787b7c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -142,6 +142,7 @@
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE" />
<protected-broadcast android:name="android.os.action.SETTING_RESTORED" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3049d3a..bd8a429 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4969,6 +4969,10 @@
<!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
<bool name="config_faceAuthDismissesKeyguard">true</bool>
+ <!-- Default value for whether a SFPS device is required to be interactive for fingerprint auth
+ to unlock the device. -->
+ <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2e46a6f..f4675200 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2723,6 +2723,7 @@
<java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
<java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
<java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+ <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
<!-- Face config -->
<java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e4..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@
@Override
public void onActivityPreCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
@@ -1934,6 +1949,11 @@
@Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1969,7 +1989,11 @@
if (who instanceof Activity) {
// We will check if the new activity should be split with the activity that launched
// it.
- launchingActivity = (Activity) who;
+ final Activity activity = (Activity) who;
+ // For Activity that is child of another Activity (ActivityGroup), treat the parent
+ // Activity as the launching one because it's window will just be a child of the
+ // parent Activity window.
+ launchingActivity = activity.isChild() ? activity.getParent() : activity;
if (isInPictureInPicture(launchingActivity)) {
// We don't embed activity when it is in PIP.
return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 7960323..362f1fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
- if (curSecondaryContainer != null
+ if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
&& (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
// Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
// the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
new file mode 100644
index 0000000..8779cc0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
new file mode 100644
index 0000000..ea0fbb0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
new file mode 100644
index 0000000..c55cbe2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
new file mode 100644
index 0000000..447df43
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
new file mode 100644
index 0000000..c334a54
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:translateX="6.0"
+ android:translateY="8.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
new file mode 100644
index 0000000..e307f00
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="210.0dp"
+ android:height="64.0dp"
+ android:tint="@color/decor_button_light_color"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
new file mode 100644
index 0000000..d9a140b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:id="@+id/handle_menu"
+android:layout_width="wrap_content"
+android:layout_height="wrap_content"
+android:gravity="center_horizontal"
+android:background="@drawable/decor_caption_title">
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/fullscreen_button"
+ android:contentDescription="@string/fullscreen_text"
+ android:background="@drawable/caption_fullscreen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/split_screen_button"
+ android:contentDescription="@string/split_screen_text"
+ android:background="@drawable/caption_split_screen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/floating_button"
+ android:contentDescription="@string/float_button_text"
+ android:background="@drawable/caption_floating_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/desktop_button"
+ android:contentDescription="@string/desktop_text"
+ android:background="@drawable/caption_desktop_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/more_button"
+ android:contentDescription="@string/more_button_text"
+ android:background="@drawable/caption_more_button"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index 38cd570..51e634c 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -19,14 +19,10 @@
android:id="@+id/caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title">
<Button
+ style="@style/CaptionButtonStyle"
android:id="@+id/back_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
android:contentDescription="@string/back_button_text"
android:background="@drawable/decor_back_button_dark"
/>
@@ -39,11 +35,8 @@
android:contentDescription="@string/handle_text"
android:background="@drawable/decor_handle_dark"/>
<Button
+ style="@style/CaptionButtonStyle"
android:id="@+id/close_window"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index d8a5074..9fab3a1 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -197,4 +197,14 @@
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
<string name="handle_text">Handle</string>
+ <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
+ <string name="fullscreen_text">Fullscreen</string>
+ <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
+ <string name="desktop_text">Desktop Mode</string>
+ <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
+ <string name="split_screen_text">Split Screen</string>
+ <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
+ <string name="more_button_text">More</string>
+ <!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
+ <string name="float_button_text">Float</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 19f7c3e..a859721 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,6 +30,13 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
+ <style name="CaptionButtonStyle">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_margin">5dp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/split_divider_bar_width</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5b7d141..295a2e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -86,9 +86,9 @@
private static final int FLING_ENTER_DURATION = 350;
private static final int FLING_EXIT_DURATION = 350;
- private final int mDividerWindowWidth;
- private final int mDividerInsets;
- private final int mDividerSize;
+ private int mDividerWindowWidth;
+ private int mDividerInsets;
+ private int mDividerSize;
private final Rect mTempRect = new Rect();
private final Rect mRootBounds = new Rect();
@@ -131,6 +131,7 @@
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
+ mDensity = configuration.densityDpi;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayImeController = displayImeController;
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
@@ -139,24 +140,22 @@
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
- final Resources resources = context.getResources();
- mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
- mDividerInsets = getDividerInsets(resources, context.getDisplay());
- mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
+ updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
resetDividerPosition();
- mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+ mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
updateInvisibleRect();
}
- private int getDividerInsets(Resources resources, Display display) {
+ private void updateDividerConfig(Context context) {
+ final Resources resources = context.getResources();
+ final Display display = context.getDisplay();
final int dividerInset = resources.getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_insets);
-
int radius = 0;
RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
@@ -167,7 +166,9 @@
corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
- return Math.max(dividerInset, radius);
+ mDividerInsets = Math.max(dividerInset, radius);
+ mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
}
/** Gets bounds of the primary split with screen based coordinate. */
@@ -309,6 +310,7 @@
mRotation = rotation;
mDensity = density;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ updateDividerConfig(mContext);
initDividerPosition(mTempRect);
updateInvisibleRect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 16f1d1c..f81c9f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.RemoteAction;
@@ -70,6 +71,11 @@
void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
/**
+ * Wait until the next frame to run the given Runnable.
+ */
+ void runWithNextFrame(@NonNull Runnable runnable);
+
+ /**
* Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
* need to synchronize the movements on the same frame as PiP.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..2d7c5ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -179,8 +179,10 @@
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
}
}
@@ -196,6 +198,34 @@
}
};
+ /**
+ * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+ *
+ * This is done to avoid a race condition between the last transaction applied in
+ * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a
+ * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it
+ * gets applied before the transaction created by the last onAnimationUpdate. As a result of
+ * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which
+ * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize
+ * operation until the next frame. This aligns the last onAnimationUpdate transaction with the
+ * WCT application.
+ *
+ * The race only happens when the PiP surface transaction has to be synced with the PiP menu
+ * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and
+ * the PiP menu contents.
+ */
+ private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+ if (!shouldSyncPipTransactionWithMenu()) {
+ finishResizeRunnable.run();
+ return;
+ }
+ mPipMenuController.runWithNextFrame(finishResizeRunnable);
+ }
+
+ private boolean shouldSyncPipTransactionWithMenu() {
+ return mPipMenuController.isMenuVisible();
+ }
+
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +251,7 @@
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1223,7 +1253,7 @@
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1265,7 +1295,7 @@
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 281ea53..27902b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -305,6 +305,18 @@
showResizeHandle);
}
+ @Override
+ public void runWithNextFrame(Runnable runnable) {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ runnable.run();
+ }
+
+ mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+ mMainHandler.post(runnable);
+ });
+ mPipMenuView.invalidate();
+ }
+
/**
* Move the PiP menu, which does a translation and possibly a scale transformation.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 616d447..d28a9f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -612,9 +612,24 @@
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
+ int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChanged(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
+ int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+ if (!mEnablePipKeepClearAlgorithm) {
+ int pipTop = mPipBoundsState.getBounds().top;
+ int diff = newMaxMovementBound - oldMaxMovementBound;
+ if (diff < 0 && pipTop > newMaxMovementBound) {
+ // bottom inset has increased, move PiP up if it is too low
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
+ newMaxMovementBound - pipTop);
+ }
+ if (diff > 0 && oldMaxMovementBound == pipTop) {
+ // bottom inset has decreased, move PiP down if it was by the edge
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
+ }
+ }
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 975d4bb..a9a97be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -427,7 +427,7 @@
// If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
// occluded by the IME or shelf.
if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting()) {
+ if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
} else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 4ce45e1..7d4b43b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -466,6 +466,18 @@
}
@Override
+ public void runWithNextFrame(Runnable runnable) {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ runnable.run();
+ }
+
+ mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+ mMainHandler.post(runnable);
+ });
+ mPipMenuView.invalidate();
+ }
+
+ @Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
Rect pipDestBounds) {
if (DEBUG) {
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 dca516a..36dd8ed 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
@@ -26,11 +26,16 @@
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.InputChannel;
import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -64,8 +69,11 @@
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
private DesktopModeController mDesktopModeController;
+ private EventReceiver mEventReceiver;
+ private InputMonitor mInputMonitor;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+ private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
public CaptionWindowDecorViewModel(
Context context,
@@ -108,12 +116,19 @@
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
+ TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
+ mDragStartListener);
CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
setupWindowDecorationForTransition(taskInfo, startT, finishT);
+ if (mInputMonitor == null) {
+ mInputMonitor = InputManager.getInstance().monitorGestureInput(
+ "caption-touch", mContext.getDisplayId());
+ mEventReceiver = new EventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ }
return true;
}
@@ -165,6 +180,7 @@
@Override
public void onClick(View v) {
+ CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -176,6 +192,15 @@
}
} else if (id == R.id.back_button) {
injectBackKey();
+ } else if (id == R.id.caption_handle) {
+ decoration.createHandleMenu();
+ } else if (id == R.id.desktop_button) {
+ mDesktopModeController.setDesktopModeActive(true);
+ decoration.closeHandleMenu();
+ } else if (id == R.id.fullscreen_button) {
+ mDesktopModeController.setDesktopModeActive(false);
+ decoration.closeHandleMenu();
+ decoration.setButtonVisibility();
}
}
private void injectBackKey() {
@@ -257,6 +282,36 @@
}
}
+ // InputEventReceiver to listen for touch input outside of caption bounds
+ private class EventReceiver extends InputEventReceiver {
+ EventReceiver(InputChannel channel, Looper looper) {
+ super(channel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ if (event instanceof MotionEvent
+ && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+ handled = true;
+ CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+ }
+ finishInputEvent(event, handled);
+ }
+ }
+
+ // If any input received is outside of caption bounds, turn off handle menu
+ private void handleMotionEvent(MotionEvent ev) {
+ int size = mWindowDecorByTaskId.size();
+ for (int i = 0; i < size; i++) {
+ CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration != null) {
+ decoration.closeHandleMenuIfNeeded(ev);
+ }
+ }
+ }
+
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
@@ -264,4 +319,11 @@
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
+
+ private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
+ @Override
+ public void onDragStart(int taskId) {
+ mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 9d61c14..03cad04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -20,10 +20,14 @@
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
@@ -58,6 +62,8 @@
private boolean mDesktopActive;
+ private AdditionalWindow mHandleMenu;
+
CaptionWindowDecoration(
Context context,
DisplayController displayController,
@@ -123,7 +129,20 @@
if (isDragResizeable) {
mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
}
+ final Resources resources = mDecorWindowContext.getResources();
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int captionHeight = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionHeightId);
+ final int captionWidth = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionWidthId);
+ final int captionLeft = taskBounds.width() / 2
+ - captionWidth / 2;
+ final int captionTop = taskBounds.top
+ <= captionHeight / 2 ? 0 : -captionHeight / 2;
+ mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
+
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+ taskInfo = null; // Clear it just in case we use it accidentally
mTaskOrganizer.applyTransaction(wct);
@@ -137,15 +156,14 @@
}
// If this task is not focused, do not show caption.
- setCaptionVisibility(taskInfo.isFocused);
+ setCaptionVisibility(mTaskInfo.isFocused);
// Only handle should show if Desktop Mode is inactive.
boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
- if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
+ if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
mDesktopActive = desktopCurrentStatus;
setButtonVisibility();
}
- taskInfo = null; // Clear it just in case we use it accidentally
if (!isDragResizeable) {
closeDragResizeListener();
@@ -184,9 +202,22 @@
back.setOnClickListener(mOnCaptionButtonClickListener);
View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
+ handle.setOnClickListener(mOnCaptionButtonClickListener);
setButtonVisibility();
}
+ private void setupHandleMenu() {
+ View menu = mHandleMenu.mWindowViewHost.getView();
+ View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
+ View desktop = menu.findViewById(R.id.desktop_button);
+ desktop.setOnClickListener(mOnCaptionButtonClickListener);
+ View split = menu.findViewById(R.id.split_screen_button);
+ split.setOnClickListener(mOnCaptionButtonClickListener);
+ View more = menu.findViewById(R.id.more_button);
+ more.setOnClickListener(mOnCaptionButtonClickListener);
+ }
+
/**
* Sets caption visibility based on task focus.
*
@@ -194,8 +225,9 @@
*/
private void setCaptionVisibility(boolean visible) {
int v = visible ? View.VISIBLE : View.GONE;
- View caption = mResult.mRootView.findViewById(R.id.caption);
- caption.setVisibility(v);
+ View captionView = mResult.mRootView.findViewById(R.id.caption);
+ captionView.setVisibility(v);
+ if (!visible) closeHandleMenu();
}
/**
@@ -203,6 +235,7 @@
*
*/
public void setButtonVisibility() {
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
View back = caption.findViewById(R.id.back_button);
@@ -220,6 +253,10 @@
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
+ public boolean isHandleMenuActive() {
+ return mHandleMenu != null;
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
@@ -228,9 +265,67 @@
mDragResizeListener = null;
}
+ /**
+ * Create and display handle menu window
+ */
+ public void createHandleMenu() {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String namePrefix = "Caption Menu";
+ mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+ x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
+ width, height);
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ setupHandleMenu();
+ }
+
+ /**
+ * Close the handle menu window
+ */
+ public void closeHandleMenu() {
+ if (!isHandleMenuActive()) return;
+ mHandleMenu.releaseView();
+ mHandleMenu = null;
+ }
+
+ @Override
+ void releaseViews() {
+ closeHandleMenu();
+ super.releaseViews();
+ }
+
+ /**
+ * Close an open handle menu if input is outside of menu coordinates
+ * @param ev the tapped point to compare against
+ * @return
+ */
+ public void closeHandleMenuIfNeeded(MotionEvent ev) {
+ if (mHandleMenu != null) {
+ Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+ .positionInParent;
+ final Resources resources = mDecorWindowContext.getResources();
+ ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+ ev.offsetLocation(-positionInParent.x, -positionInParent.y);
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ if (!(ev.getX() >= 0 && ev.getY() >= 0
+ && ev.getX() <= width && ev.getY() <= height)) {
+ closeHandleMenu();
+ }
+ }
+ }
+
@Override
public void close() {
closeDragResizeListener();
+ closeHandleMenu();
super.close();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 27c1011..f0f2db7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -42,14 +42,18 @@
private final Rect mResizeTaskBounds = new Rect();
private int mCtrlType;
+ private DragStartListener mDragStartListener;
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
+ mDragStartListener = dragStartListener;
}
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
@@ -97,4 +101,12 @@
mTaskOrganizer.applyTransaction(wct);
}
}
+
+ interface DragStartListener {
+ /**
+ * Inform the implementing class that a drag resize has started
+ * @param taskId id of this positioner's {@link WindowDecoration}
+ */
+ void onDragStart(int taskId);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index b314163..7ecb3f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -200,16 +200,17 @@
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
final Resources resources = mDecorWindowContext.getResources();
- final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
- final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
+ outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+ outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
+ loadDimensionPixelSize(resources, params.mOutsetRightId)
- - decorContainerOffsetX;
+ - outResult.mDecorContainerOffsetX;
outResult.mHeight = taskBounds.height()
+ loadDimensionPixelSize(resources, params.mOutsetBottomId)
- - decorContainerOffsetY;
+ - outResult.mDecorContainerOffsetY;
startT.setPosition(
- mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+ mDecorationContainerSurface,
+ outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
.setWindowCrop(mDecorationContainerSurface,
outResult.mWidth, outResult.mHeight)
// TODO(b/244455401): Change the z-order when it's better organized
@@ -252,14 +253,11 @@
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
- //Prevent caption from going offscreen if task is too high up
- final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
-
startT.setPosition(
- mCaptionContainerSurface, -decorContainerOffsetX
- + taskBounds.width() / 2 - captionWidth / 2,
- -decorContainerOffsetY - captionYPos)
- .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
+ mCaptionContainerSurface,
+ -outResult.mDecorContainerOffsetX + params.mCaptionX,
+ -outResult.mDecorContainerOffsetY + params.mCaptionY)
+ .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
@@ -292,7 +290,7 @@
// Caption insets
mCaptionInsetsRect.set(taskBounds);
mCaptionInsetsRect.bottom =
- mCaptionInsetsRect.top + captionHeight - captionYPos;
+ mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
CAPTION_INSETS_TYPES);
} else {
@@ -302,10 +300,10 @@
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
mTaskSurfaceCrop.set(
- decorContainerOffsetX,
- decorContainerOffsetY,
- outResult.mWidth + decorContainerOffsetX,
- outResult.mHeight + decorContainerOffsetY);
+ outResult.mDecorContainerOffsetX,
+ outResult.mDecorContainerOffsetY,
+ outResult.mWidth + outResult.mDecorContainerOffsetX,
+ outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
.setCrop(mTaskSurface, mTaskSurfaceCrop);
@@ -326,7 +324,7 @@
return true;
}
- private void releaseViews() {
+ void releaseViews() {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -369,20 +367,60 @@
releaseViews();
}
- private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+ static int loadDimensionPixelSize(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimensionPixelSize(resourceId);
}
- private static float loadDimension(Resources resources, int resourceId) {
+ static float loadDimension(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimension(resourceId);
}
+ /**
+ * Create a window associated with this WindowDecoration.
+ * Note that subclass must dispose of this when the task is hidden/closed.
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
+ * @return
+ */
+ AdditionalWindow addWindow(int layoutId, String namePrefix,
+ SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ SurfaceControl windowSurfaceControl = builder
+ .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setParent(mDecorationContainerSurface)
+ .build();
+ View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+
+ t.setPosition(
+ windowSurfaceControl, xPos, yPos)
+ .setWindowCrop(windowSurfaceControl, width, height)
+ .show(windowSurfaceControl);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+ windowSurfaceControl, null /* hostInputToken */);
+ SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
+ .create(mDecorWindowContext, mDisplay, windowManager);
+ viewHost.setView(v, lp);
+ return new AdditionalWindow(windowSurfaceControl, viewHost,
+ mSurfaceControlTransactionSupplier);
+ }
+
static class RelayoutParams{
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
@@ -395,6 +433,9 @@
int mOutsetLeftId;
int mOutsetRightId;
+ int mCaptionX;
+ int mCaptionY;
+
void setOutsets(int leftId, int topId, int rightId, int bottomId) {
mOutsetLeftId = leftId;
mOutsetTopId = topId;
@@ -402,6 +443,11 @@
mOutsetBottomId = bottomId;
}
+ void setCaptionPosition(int left, int top) {
+ mCaptionX = left;
+ mCaptionY = top;
+ }
+
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
@@ -412,6 +458,9 @@
mOutsetBottomId = Resources.ID_NULL;
mOutsetLeftId = Resources.ID_NULL;
mOutsetRightId = Resources.ID_NULL;
+
+ mCaptionX = 0;
+ mCaptionY = 0;
}
}
@@ -419,10 +468,14 @@
int mWidth;
int mHeight;
T mRootView;
+ int mDecorContainerOffsetX;
+ int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
+ mDecorContainerOffsetX = 0;
+ mDecorContainerOffsetY = 0;
mRootView = null;
}
}
@@ -432,4 +485,41 @@
return new SurfaceControlViewHost(c, d, wmm);
}
}
+
+ /**
+ * Subclass for additional windows associated with this WindowDecoration
+ */
+ static class AdditionalWindow {
+ SurfaceControl mWindowSurface;
+ SurfaceControlViewHost mWindowViewHost;
+ Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+ private AdditionalWindow(SurfaceControl surfaceControl,
+ SurfaceControlViewHost surfaceControlViewHost,
+ Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ mWindowSurface = surfaceControl;
+ mWindowViewHost = surfaceControlViewHost;
+ mTransactionSupplier = transactionSupplier;
+ }
+
+ void releaseView() {
+ WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
+
+ if (mWindowViewHost != null) {
+ mWindowViewHost.release();
+ mWindowViewHost = null;
+ }
+ windowManager = null;
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ boolean released = false;
+ if (mWindowSurface != null) {
+ t.remove(mWindowSurface);
+ mWindowSurface = null;
+ released = true;
+ }
+ if (released) {
+ t.apply();
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 4d37e5d..15181b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -35,6 +35,7 @@
import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -64,6 +65,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -102,12 +104,14 @@
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+ private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
@Before
public void setUp() {
mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
+ mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
@@ -227,8 +231,8 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
- verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -242,7 +246,7 @@
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction)
.addRectInsetsProvider(taskInfo.token,
- new Rect(100, 300, 400, 332),
+ new Rect(100, 300, 400, 364),
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
@@ -366,6 +370,71 @@
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
+ @Test
+ public void testAddWindow() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ windowDecor.relayout(taskInfo);
+
+ final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder additionalWindowSurfaceBuilder =
+ createMockSurfaceControlBuilder(additionalWindowSurface);
+ mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
+
+ WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
+
+ verify(additionalWindowSurfaceBuilder).setContainerLayer();
+ verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
+ verify(additionalWindowSurfaceBuilder).build();
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+ verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
+ verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
+ .create(any(), eq(defaultDisplay), any());
+ assertThat(additionalWindow.mWindowViewHost).isNotNull();
+
+ additionalWindow.releaseView();
+
+ assertThat(additionalWindow.mWindowViewHost).isNull();
+ assertThat(additionalWindow.mWindowSurface).isNull();
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -429,5 +498,20 @@
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
+
+ private WindowDecoration.AdditionalWindow addTestWindow() {
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String name = "Test Window";
+ WindowDecoration.AdditionalWindow additionalWindow =
+ addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+ x - mRelayoutResult.mDecorContainerOffsetX,
+ y - mRelayoutResult.mDecorContainerOffsetY,
+ width, height);
+ return additionalWindow;
+ }
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 453a713..8946516 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index a39735f..cbf7953 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index e30d441..8d44315 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -35,4 +35,6 @@
<!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
fade_out_complete_frame -->
<dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
+
+ <integer name="qs_carrier_max_em">7</integer>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 4613e8b..bfbe88c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,6 +20,7 @@
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import com.android.systemui.shared.recents.ISystemUiProxy;
oneway interface IOverviewProxy {
@@ -44,12 +45,6 @@
void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8;
/**
- * Sent when there was an action on one of the onboarding tips view.
- * TODO: Move this implementation to SystemUI completely
- */
- void onTip(int actionType, int viewType) = 10;
-
- /**
* Sent when device assistant changes its default assistant whether it is available or not.
*/
void onAssistantAvailable(boolean available) = 13;
@@ -60,13 +55,6 @@
void onAssistantVisibilityChanged(float visibility) = 14;
/**
- * Sent when back is triggered.
- * TODO: Move this implementation to SystemUI completely
- */
- void onBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) = 15;
-
- /**
* Sent when some system ui state changes.
*/
void onSystemUiStateChanged(int stateFlags) = 16;
@@ -110,4 +98,14 @@
* Sent when screen started turning off.
*/
void onScreenTurningOff() = 24;
+
+ /**
+ * Sent when split keyboard shortcut is triggered to enter stage split.
+ */
+ void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+
+ /**
+ * Sent when the surface for navigation bar is created or changed
+ */
+ void onNavigationBarSurface(in SurfaceControl surface) = 26;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2b2b05ce..b99b72b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -106,9 +106,6 @@
/** Sets home rotation enabled. */
void setHomeRotationEnabled(boolean enabled) = 45;
- /** Notifies that a swipe-up gesture has started */
- oneway void notifySwipeUpGestureStarted() = 46;
-
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8..a0206f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@
val keyguardOccluded: Boolean,
val occludingAppRequestingFp: Boolean,
val primaryUser: Boolean,
+ val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 93ee151..c756a17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -89,6 +89,7 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
@@ -136,6 +137,7 @@
private GlobalSettings mGlobalSettings;
private FalsingManager mFalsingManager;
private UserSwitcherController mUserSwitcherController;
+ private FalsingA11yDelegate mFalsingA11yDelegate;
private AlertDialog mAlertDialog;
private boolean mSwipeUpToRetry;
@@ -318,7 +320,8 @@
void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
- UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) {
+ UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback,
+ FalsingA11yDelegate falsingA11yDelegate) {
if (mCurrentMode == mode) return;
Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to "
+ modeToString(mode));
@@ -337,6 +340,7 @@
}
mGlobalSettings = globalSettings;
mFalsingManager = falsingManager;
+ mFalsingA11yDelegate = falsingA11yDelegate;
mUserSwitcherController = userSwitcherController;
setupViewMode();
}
@@ -361,7 +365,7 @@
}
mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingManager,
- mUserSwitcherController);
+ mUserSwitcherController, mFalsingA11yDelegate);
}
@Mode int getMode() {
@@ -723,7 +727,8 @@
default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {};
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {};
/** Reinitialize the location */
default void updateSecurityViewLocation() {};
@@ -828,7 +833,8 @@
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {
mView = v;
mViewFlipper = viewFlipper;
@@ -865,6 +871,7 @@
this::setupUserSwitcher;
private UserSwitcherCallback mUserSwitcherCallback;
+ private FalsingA11yDelegate mFalsingA11yDelegate;
UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
mUserSwitcherCallback = userSwitcherCallback;
@@ -874,13 +881,15 @@
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {
init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false);
mView = v;
mViewFlipper = viewFlipper;
mFalsingManager = falsingManager;
mUserSwitcherController = userSwitcherController;
mResources = v.getContext().getResources();
+ mFalsingA11yDelegate = falsingA11yDelegate;
if (mUserSwitcherViewGroup == null) {
LayoutInflater.from(v.getContext()).inflate(
@@ -978,6 +987,7 @@
mUserSwitcher.setText(currentUserName);
KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
+ anchor.setAccessibilityDelegate(mFalsingA11yDelegate);
BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
@Override
@@ -1048,7 +1058,7 @@
anchor.setOnClickListener((v) -> {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
- mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
+ mPopup = new KeyguardUserSwitcherPopupMenu(mView.getContext(), mFalsingManager);
mPopup.setAnchorView(anchor);
mPopup.setAdapter(adapter);
mPopup.setOnItemClickListener((parent, view, pos, id) -> {
@@ -1137,7 +1147,8 @@
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
- @NonNull UserSwitcherController userSwitcherController) {
+ @NonNull UserSwitcherController userSwitcherController,
+ @NonNull FalsingA11yDelegate falsingA11yDelegate) {
init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true);
mView = v;
mViewFlipper = viewFlipper;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 0b395a8..79a01b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -59,6 +59,7 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -100,6 +101,7 @@
private final FeatureFlags mFeatureFlags;
private final SessionTracker mSessionTracker;
private final Optional<SidefpsController> mSidefpsController;
+ private final FalsingA11yDelegate mFalsingA11yDelegate;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -288,7 +290,8 @@
FeatureFlags featureFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
- Optional<SidefpsController> sidefpsController) {
+ Optional<SidefpsController> sidefpsController,
+ FalsingA11yDelegate falsingA11yDelegate) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -309,6 +312,7 @@
mGlobalSettings = globalSettings;
mSessionTracker = sessionTracker;
mSidefpsController = sidefpsController;
+ mFalsingA11yDelegate = falsingA11yDelegate;
}
@Override
@@ -349,10 +353,21 @@
if (!mSidefpsController.isPresent()) {
return;
}
- if (mBouncerVisible
- && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
- && mUpdateMonitor.isFingerprintDetectionRunning()
- && !mUpdateMonitor.userNeedsStrongAuth()) {
+ final boolean sfpsEnabled = getResources().getBoolean(
+ R.bool.config_show_sidefps_hint_on_bouncer);
+ final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
+ final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth();
+
+ boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth;
+
+ if (DEBUG) {
+ Log.d(TAG, "sideFpsToShow=" + toShow + ", "
+ + "mBouncerVisible=" + mBouncerVisible + ", "
+ + "configEnabled=" + sfpsEnabled + ", "
+ + "fpsDetectionRunning=" + fpsDetectionRunning + ", "
+ + "needsStrongAuth=" + needsStrongAuth);
+ }
+ if (toShow) {
mSidefpsController.get().show();
} else {
mSidefpsController.get().hide();
@@ -625,7 +640,7 @@
mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController,
() -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue),
- null));
+ null), mFalsingA11yDelegate);
}
public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
@@ -730,6 +745,7 @@
private final UserSwitcherController mUserSwitcherController;
private final SessionTracker mSessionTracker;
private final Optional<SidefpsController> mSidefpsController;
+ private final FalsingA11yDelegate mFalsingA11yDelegate;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -749,7 +765,8 @@
FeatureFlags featureFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
- Optional<SidefpsController> sidefpsController) {
+ Optional<SidefpsController> sidefpsController,
+ FalsingA11yDelegate falsingA11yDelegate) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -767,6 +784,7 @@
mUserSwitcherController = userSwitcherController;
mSessionTracker = sessionTracker;
mSidefpsController = sidefpsController;
+ mFalsingA11yDelegate = falsingA11yDelegate;
}
public KeyguardSecurityContainerController create(
@@ -777,7 +795,7 @@
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker,
- mSidefpsController);
+ mSidefpsController, mFalsingA11yDelegate);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bad75e8..558869c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -151,6 +151,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Lists;
@@ -337,17 +338,20 @@
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
+ private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -396,6 +400,7 @@
protected Handler getHandler() {
return mHandler;
}
+
private final Handler mHandler;
private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -720,6 +725,7 @@
/**
* Request to listen for face authentication when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request face auth listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFace()}
@@ -732,6 +738,7 @@
/**
* Request to listen for fingerprint when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request fingerprint listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)}
@@ -1946,6 +1953,7 @@
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
+ SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -1988,6 +1996,7 @@
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
+ mSecureSettings = secureSettings;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2229,9 +2238,35 @@
Settings.System.TIME_12_24)));
}
};
+
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+ updateSfpsRequireScreenOnToAuthPref();
+ mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSfpsRequireScreenOnToAuthPref();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ mSecureSettings.getUriFor(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+ false,
+ mSfpsRequireScreenOnToAuthPrefObserver,
+ getCurrentUser());
+ }
+
+ protected void updateSfpsRequireScreenOnToAuthPref() {
+ final int defaultSfpsRequireScreenOnToAuthValue =
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+ mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ defaultSfpsRequireScreenOnToAuthValue,
+ getCurrentUser()) != 0;
}
private void initializeSimState() {
@@ -2276,6 +2311,22 @@
}
/**
+ * @return true if there's at least one sfps enrollment for the current user.
+ */
+ public boolean isSfpsEnrolled() {
+ return mAuthController.isSfpsEnrolled(getCurrentUser());
+ }
+
+ /**
+ * @return true if sfps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isSfpsSupported() {
+ return mAuthController.getSfpsProps() != null
+ && !mAuthController.getSfpsProps().isEmpty();
+ }
+
+ /**
* @return true if there's at least one face enrolled
*/
public boolean isFaceEnrolled() {
@@ -2598,13 +2649,21 @@
!(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
- && userDoesNotHaveTrust);
+ && !isEncryptedOrLockdownForUser
+ && userDoesNotHaveTrust);
- final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+ boolean shouldListenSideFpsState = true;
+ if (isSfpsSupported() && isSfpsEnrolled()) {
+ shouldListenSideFpsState =
+ mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+ }
+
+ boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+ && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenSideFpsState;
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
@@ -2626,6 +2685,7 @@
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
+ shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
@@ -3727,6 +3787,11 @@
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
+ if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mSfpsRequireScreenOnToAuthPrefObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3799,6 +3864,11 @@
pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+ } else if (isSfpsSupported()) {
+ pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
+ pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
+ pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
+ + mSfpsRequireScreenOnToAuthPrefEnabled);
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 80c6c48..0a2d8ec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -120,7 +120,7 @@
@VisibleForTesting final BiometricCallback mBiometricCallback;
@Nullable private AuthBiometricView mBiometricView;
- @Nullable private AuthCredentialView mCredentialView;
+ @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
private final AuthPanelController mPanelController;
private final FrameLayout mFrameLayout;
private final ImageView mBackgroundView;
@@ -762,6 +762,12 @@
}
mContainerState = STATE_ANIMATING_OUT;
+ // Request hiding soft-keyboard before animating away credential UI, in case IME insets
+ // animation get delayed by dismissing animation.
+ if (isAttachedToWindow() && getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+
if (sendReason) {
mPendingCallbackReason = reason;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c015a21..e006bef 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -150,6 +150,7 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+ @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -325,6 +326,16 @@
}
}
}
+
+ if (mSidefpsProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+ } else {
+ for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+ if (prop.sensorId == sensorId) {
+ mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged();
}
@@ -677,6 +688,7 @@
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mSfpsEnrolledForUser = new SparseBooleanArray();
mOrientationListener = new BiometricDisplayListener(
context,
@@ -889,6 +901,11 @@
return mUdfpsProps;
}
+ @Nullable
+ public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+ return mSidefpsProps;
+ }
+
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
@@ -1013,6 +1030,17 @@
return mUdfpsEnrolledForUser.get(userId);
}
+ /**
+ * Whether the passed userId has enrolled SFPS.
+ */
+ public boolean isSfpsEnrolled(int userId) {
+ if (mSidefpsController == null) {
+ return false;
+ }
+
+ return mSfpsEnrolledForUser.get(userId);
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index f9e44a0..85cb398 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.util.AttributeSet;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
@@ -34,7 +35,7 @@
*/
public class AuthCredentialPatternView extends AuthCredentialView {
- private LockPatternView mLockPatternView;
+ @VisibleForTesting LockPatternView mLockPatternView;
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
@@ -93,9 +94,7 @@
@Override
protected void onErrorTimeoutFinish() {
super.onErrorTimeoutFinish();
- // select to enable marquee unless a screen reader is enabled
- mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
+ mLockPatternView.setEnabled(true);
}
public AuthCredentialPatternView(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 5958e6a..157f14f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -47,6 +47,7 @@
import android.widget.TextView;
import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -98,7 +99,7 @@
protected int mUserId;
protected long mOperationId;
protected int mEffectiveUserId;
- protected ErrorTimer mErrorTimer;
+ @VisibleForTesting ErrorTimer mErrorTimer;
protected @Background DelayableExecutor mBackgroundExecutor;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 96fe65f..65fcd76 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -60,7 +60,9 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -119,6 +121,7 @@
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final VibratorHelper mVibrator;
+ @NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -130,6 +133,7 @@
@NonNull private final LatencyTracker mLatencyTracker;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ @NonNull private final BouncerInteractor mBouncerInteractor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -212,7 +216,8 @@
mUnlockedScreenOffAnimationController,
mUdfpsDisplayMode, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator)));
+ fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
+ mBouncerInteractor)));
}
@Override
@@ -590,6 +595,7 @@
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull FeatureFlags featureFlags,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -608,7 +614,8 @@
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
- @BiometricsBackground Executor biometricsExecutor) {
+ @BiometricsBackground Executor biometricsExecutor,
+ @NonNull BouncerInteractor bouncerInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -638,6 +645,8 @@
mActivityLaunchAnimator = activityLaunchAnimator;
mAlternateTouchProvider = alternateTouchProvider.orElse(null);
mBiometricExecutor = biometricsExecutor;
+ mFeatureFlags = featureFlags;
+ mBouncerInteractor = bouncerInteractor;
mOrientationListener = new BiometricDisplayListener(
context,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7d01096..d70861a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -48,6 +48,8 @@
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -70,29 +72,31 @@
*/
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
- private val context: Context,
- fingerprintManager: FingerprintManager,
- private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
- private val accessibilityManager: AccessibilityManager,
- private val statusBarStateController: StatusBarStateController,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager,
- private val transitionController: LockscreenShadeTransitionController,
- private val configurationController: ConfigurationController,
- private val systemClock: SystemClock,
- private val keyguardStateController: KeyguardStateController,
- private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
- private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
- val requestId: Long,
- @ShowReason val requestReason: Int,
- private val controllerCallback: IUdfpsOverlayControllerCallback,
- private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
+ private val context: Context,
+ fingerprintManager: FingerprintManager,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val accessibilityManager: AccessibilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dialogManager: SystemUIDialogManager,
+ private val dumpManager: DumpManager,
+ private val transitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ val requestId: Long,
+ @ShowReason val requestReason: Int,
+ private val controllerCallback: IUdfpsOverlayControllerCallback,
+ private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -246,7 +250,9 @@
unlockedScreenOffAnimationController,
dialogManager,
controller,
- activityLaunchAnimator
+ activityLaunchAnimator,
+ featureFlags,
+ bouncerInteractor
)
}
REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
deleted file mode 100644
index 4d7f89d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.util.MathUtils;
-import android.view.MotionEvent;
-
-import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-
-import java.io.PrintWriter;
-
-/**
- * Class that coordinates non-HBM animations during keyguard authentication.
- */
-public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
- public static final String TAG = "UdfpsKeyguardViewCtrl";
- @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
- @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
- @NonNull private final ConfigurationController mConfigurationController;
- @NonNull private final SystemClock mSystemClock;
- @NonNull private final KeyguardStateController mKeyguardStateController;
- @NonNull private final UdfpsController mUdfpsController;
- @NonNull private final UnlockedScreenOffAnimationController
- mUnlockedScreenOffAnimationController;
- @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
- private final ValueAnimator mUnlockedScreenOffDozeAnimator = ValueAnimator.ofFloat(0f, 1f);
-
- private boolean mShowingUdfpsBouncer;
- private boolean mUdfpsRequested;
- private float mQsExpansion;
- private boolean mFaceDetectRunning;
- private int mStatusBarState;
- private float mTransitionToFullShadeProgress;
- private float mLastDozeAmount;
- private long mLastUdfpsBouncerShowTime = -1;
- private float mPanelExpansionFraction;
- private boolean mLaunchTransitionFadingAway;
- private boolean mIsLaunchingActivity;
- private float mActivityLaunchProgress;
-
- /**
- * hidden amount of pin/pattern/password bouncer
- * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
- * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f)
- */
- private float mInputBouncerHiddenAmount;
- private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible
-
- protected UdfpsKeyguardViewController(
- @NonNull UdfpsKeyguardView view,
- @NonNull StatusBarStateController statusBarStateController,
- @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
- @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DumpManager dumpManager,
- @NonNull LockscreenShadeTransitionController transitionController,
- @NonNull ConfigurationController configurationController,
- @NonNull SystemClock systemClock,
- @NonNull KeyguardStateController keyguardStateController,
- @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- @NonNull SystemUIDialogManager systemUIDialogManager,
- @NonNull UdfpsController udfpsController,
- @NonNull ActivityLaunchAnimator activityLaunchAnimator) {
- super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
- dumpManager);
- mKeyguardViewManager = statusBarKeyguardViewManager;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLockScreenShadeTransitionController = transitionController;
- mConfigurationController = configurationController;
- mSystemClock = systemClock;
- mKeyguardStateController = keyguardStateController;
- mUdfpsController = udfpsController;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- mActivityLaunchAnimator = activityLaunchAnimator;
-
- mUnlockedScreenOffDozeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mUnlockedScreenOffDozeAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mUnlockedScreenOffDozeAnimator.addUpdateListener(
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mView.onDozeAmountChanged(
- animation.getAnimatedFraction(),
- (float) animation.getAnimatedValue(),
- UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
- }
- });
- }
-
- @Override
- @NonNull protected String getTag() {
- return "UdfpsKeyguardViewController";
- }
-
- @Override
- public void onInit() {
- super.onInit();
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- }
-
- @Override
- protected void onViewAttached() {
- super.onViewAttached();
- final float dozeAmount = getStatusBarStateController().getDozeAmount();
- mLastDozeAmount = dozeAmount;
- mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
- getStatusBarStateController().addCallback(mStateListener);
-
- mUdfpsRequested = false;
-
- mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
- mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
- mStatusBarState = getStatusBarStateController().getState();
- mQsExpansion = mKeyguardViewManager.getQsExpansion();
- updateGenericBouncerVisibility();
- mConfigurationController.addCallback(mConfigurationListener);
- getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
- updateScaleFactor();
- mView.updatePadding();
- updateAlpha();
- updatePauseAuth();
-
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
- mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- protected void onViewDetached() {
- super.onViewDetached();
- mFaceDetectRunning = false;
-
- mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
- getStatusBarStateController().removeCallback(mStateListener);
- mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- mConfigurationController.removeCallback(mConfigurationListener);
- getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
- if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
- }
- mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- super.dump(pw, args);
- pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer);
- pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
- pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState));
- pw.println("mTransitionToFullShadeProgress=" + mTransitionToFullShadeProgress);
- pw.println("mQsExpansion=" + mQsExpansion);
- pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing);
- pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
- pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction);
- pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
- pw.println("mUdfpsRequested=" + mUdfpsRequested);
- pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
- pw.println("mLastDozeAmount=" + mLastDozeAmount);
-
- mView.dump(pw);
- }
-
- /**
- * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
- * @return whether the udfpsBouncer has been newly shown or hidden
- */
- private boolean showUdfpsBouncer(boolean show) {
- if (mShowingUdfpsBouncer == show) {
- return false;
- }
-
- boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
- mShowingUdfpsBouncer = show;
- if (mShowingUdfpsBouncer) {
- mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
- }
- if (mShowingUdfpsBouncer) {
- if (udfpsAffordanceWasNotShowing) {
- mView.animateInUdfpsBouncer(null);
- }
-
- if (mKeyguardStateController.isOccluded()) {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
- }
-
- mView.announceForAccessibility(mView.getContext().getString(
- R.string.accessibility_fingerprint_bouncer));
- } else {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- }
-
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- return true;
- }
-
- /**
- * Returns true if the fingerprint manager is running but we want to temporarily pause
- * authentication. On the keyguard, we may want to show udfps when the shade
- * is expanded, so this can be overridden with the showBouncer method.
- */
- public boolean shouldPauseAuth() {
- if (mShowingUdfpsBouncer) {
- return false;
- }
-
- if (mUdfpsRequested && !getNotificationShadeVisible()
- && (!mIsGenericBouncerShowing
- || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)
- && mKeyguardStateController.isShowing()) {
- return false;
- }
-
- if (mLaunchTransitionFadingAway) {
- return true;
- }
-
- // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
- // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
- // delayed. However, we still animate in the UDFPS affordance with the
- // mUnlockedScreenOffDozeAnimator.
- if (mStatusBarState != KEYGUARD && mLastDozeAmount == 0f) {
- return true;
- }
-
- if (mInputBouncerHiddenAmount < .5f) {
- return true;
- }
-
- if (mView.getUnpausedAlpha() < (255 * .1)) {
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean listenForTouchesOutsideView() {
- return true;
- }
-
- @Override
- public void onTouchOutsideView() {
- maybeShowInputBouncer();
- }
-
- /**
- * If we were previously showing the udfps bouncer, hide it and instead show the regular
- * (pin/pattern/password) bouncer.
- *
- * Does nothing if we weren't previously showing the UDFPS bouncer.
- */
- private void maybeShowInputBouncer() {
- if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
- mKeyguardViewManager.showBouncer(true);
- }
- }
-
- /**
- * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
- * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
- * bouncer.
- */
- private boolean hasUdfpsBouncerShownWithMinTime() {
- return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
- }
-
- /**
- * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
- * transitioning yet, while 1.0f means we've fully dragged down.
- *
- * For example, start swiping down to expand the notification shade from the empty space in
- * the middle of the lock screen.
- */
- public void setTransitionToFullShadeProgress(float progress) {
- mTransitionToFullShadeProgress = progress;
- updateAlpha();
- }
-
- /**
- * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's
- * alpha is based on the doze amount.
- */
- @Override
- public void updateAlpha() {
- // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
- // then the keyguard is occluded by some application - so instead use the input bouncer
- // hidden amount to determine the fade.
- float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mPanelExpansionFraction;
-
- int alpha = mShowingUdfpsBouncer ? 255
- : (int) MathUtils.constrain(
- MathUtils.map(.5f, .9f, 0f, 255f, expansion),
- 0f, 255f);
-
- if (!mShowingUdfpsBouncer) {
- // swipe from top of the lockscreen to expand full QS:
- alpha *= (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(mQsExpansion));
-
- // swipe from the middle (empty space) of lockscreen to expand the notification shade:
- alpha *= (1.0f - mTransitionToFullShadeProgress);
-
- // Fade out the icon if we are animating an activity launch over the lockscreen and the
- // activity didn't request the UDFPS.
- if (mIsLaunchingActivity && !mUdfpsRequested) {
- alpha *= (1.0f - mActivityLaunchProgress);
- }
-
- // Fade out alpha when a dialog is shown
- // Fade in alpha when a dialog is hidden
- alpha *= mView.getDialogSuggestedAlpha();
- }
- mView.setUnpausedAlpha(alpha);
- }
-
- /**
- * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the
- * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not.
- */
- private void updateGenericBouncerVisibility() {
- mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer
- final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth();
- if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) {
- mInputBouncerHiddenAmount = 1f;
- } else if (mIsGenericBouncerShowing) {
- // input bouncer is fully showing
- mInputBouncerHiddenAmount = 0f;
- }
- }
-
- /**
- * Update the scale factor based on the device's resolution.
- */
- private void updateScaleFactor() {
- if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) {
- mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor());
- }
- }
-
- private final StatusBarStateController.StateListener mStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- if (mLastDozeAmount < linear) {
- showUdfpsBouncer(false);
- }
- mUnlockedScreenOffDozeAnimator.cancel();
- final boolean animatingFromUnlockedScreenOff =
- mUnlockedScreenOffAnimationController.isAnimationPlaying();
- if (animatingFromUnlockedScreenOff && linear != 0f) {
- // we manually animate the fade in of the UDFPS icon since the unlocked
- // screen off animation prevents the doze amounts to be incrementally eased in
- mUnlockedScreenOffDozeAnimator.start();
- } else {
- mView.onDozeAmountChanged(linear, eased,
- UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
- }
-
- mLastDozeAmount = linear;
- updatePauseAuth();
- }
-
- @Override
- public void onStateChanged(int statusBarState) {
- mStatusBarState = statusBarState;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
- new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
- @Override
- public boolean showAlternateAuthBouncer() {
- return showUdfpsBouncer(true);
- }
-
- @Override
- public boolean hideAlternateAuthBouncer() {
- return showUdfpsBouncer(false);
- }
-
- @Override
- public boolean isShowingAlternateAuthBouncer() {
- return mShowingUdfpsBouncer;
- }
-
- @Override
- public void requestUdfps(boolean request, int color) {
- mUdfpsRequested = request;
- mView.requestUdfps(request, color);
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean isAnimating() {
- return false;
- }
-
- /**
- * Set the amount qs is expanded. Forxample, swipe down from the top of the
- * lock screen to start the full QS expansion.
- */
- @Override
- public void setQsExpansion(float qsExpansion) {
- mQsExpansion = qsExpansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean onTouch(MotionEvent event) {
- if (mTransitionToFullShadeProgress != 0) {
- return false;
- }
- return mUdfpsController.onTouch(event);
- }
-
- @Override
- public void setBouncerExpansionChanged(float expansion) {
- mInputBouncerHiddenAmount = expansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- /**
- * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer
- * visibility changes.
- */
- @Override
- public void onBouncerVisibilityChanged() {
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.println(getTag());
- }
- };
-
- private final ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onUiModeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onThemeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- updateScaleFactor();
- mView.updatePadding();
- mView.updateColor();
- }
- };
-
- private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
- @Override
- public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
- float fraction = event.getFraction();
- mPanelExpansionFraction =
- mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
- .aboutToShowBouncerProgress(fraction) : fraction;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onLaunchTransitionFadingAwayChanged() {
- mLaunchTransitionFadingAway =
- mKeyguardStateController.isLaunchTransitionFadingAway();
- updatePauseAuth();
- }
- };
-
- private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
- new ActivityLaunchAnimator.Listener() {
- @Override
- public void onLaunchAnimationStart() {
- mIsLaunchingActivity = true;
- mActivityLaunchProgress = 0f;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationEnd() {
- mIsLaunchingActivity = false;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationProgress(float linearProgress) {
- mActivityLaunchProgress = linearProgress;
- updateAlpha();
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..5bae2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.animation.ValueAnimator
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+open class UdfpsKeyguardViewController
+constructor(
+ private val view: UdfpsKeyguardView,
+ statusBarStateController: StatusBarStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val keyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ dumpManager: DumpManager,
+ private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ systemUIDialogManager: SystemUIDialogManager,
+ private val udfpsController: UdfpsController,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor
+) :
+ UdfpsAnimationViewController<UdfpsKeyguardView>(
+ view,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager
+ ) {
+ private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
+ private var showingUdfpsBouncer = false
+ private var udfpsRequested = false
+ private var qsExpansion = 0f
+ private var faceDetectRunning = false
+ private var statusBarState = 0
+ private var transitionToFullShadeProgress = 0f
+ private var lastDozeAmount = 0f
+ private var lastUdfpsBouncerShowTime: Long = -1
+ private var panelExpansionFraction = 0f
+ private var launchTransitionFadingAway = false
+ private var isLaunchingActivity = false
+ private var activityLaunchProgress = 0f
+ private val unlockedScreenOffDozeAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
+ interpolator = Interpolators.ALPHA_IN
+ addUpdateListener { animation ->
+ view.onDozeAmountChanged(
+ animation.animatedFraction,
+ animation.animatedValue as Float,
+ UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF
+ )
+ }
+ }
+ /**
+ * Hidden amount of input (pin/pattern/password) bouncer. This is used
+ * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only
+ * used for the non-modernBouncer.
+ */
+ private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN
+ private var inputBouncerExpansion = 0f // only used for modernBouncer
+
+ private val stateListener: StatusBarStateController.StateListener =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ if (lastDozeAmount < linear) {
+ showUdfpsBouncer(false)
+ }
+ unlockedScreenOffDozeAnimator.cancel()
+ val animatingFromUnlockedScreenOff =
+ unlockedScreenOffAnimationController.isAnimationPlaying()
+ if (animatingFromUnlockedScreenOff && linear != 0f) {
+ // we manually animate the fade in of the UDFPS icon since the unlocked
+ // screen off animation prevents the doze amounts to be incrementally eased in
+ unlockedScreenOffDozeAnimator.start()
+ } else {
+ view.onDozeAmountChanged(
+ linear,
+ eased,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+ )
+ }
+ lastDozeAmount = linear
+ updatePauseAuth()
+ }
+
+ override fun onStateChanged(statusBarState: Int) {
+ this@UdfpsKeyguardViewController.statusBarState = statusBarState
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val bouncerExpansionCallback: BouncerExpansionCallback =
+ object : BouncerExpansionCallback {
+ override fun onExpansionChanged(expansion: Float) {
+ inputBouncerHiddenAmount = expansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun onVisibilityChanged(isVisible: Boolean) {
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val configurationListener: ConfigurationController.ConfigurationListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ view.updateColor()
+ }
+
+ override fun onThemeChanged() {
+ view.updateColor()
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ updateScaleFactor()
+ view.updatePadding()
+ view.updateColor()
+ }
+ }
+
+ private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
+ panelExpansionFraction =
+ if (keyguardViewManager.isBouncerInTransit) {
+ aboutToShowBouncerProgress(fraction)
+ } else {
+ fraction
+ }
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+ object : KeyguardStateController.Callback {
+ override fun onLaunchTransitionFadingAwayChanged() {
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ updatePauseAuth()
+ }
+ }
+
+ private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
+ object : ActivityLaunchAnimator.Listener {
+ override fun onLaunchAnimationStart() {
+ isLaunchingActivity = true
+ activityLaunchProgress = 0f
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationEnd() {
+ isLaunchingActivity = false
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationProgress(linearProgress: Float) {
+ activityLaunchProgress = linearProgress
+ updateAlpha()
+ }
+ }
+
+ private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
+ object : KeyguardViewManagerCallback {
+ override fun onQSExpansionChanged(qsExpansion: Float) {
+ this@UdfpsKeyguardViewController.qsExpansion = qsExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ /**
+ * Forward touches to the UdfpsController. This allows the touch to start from outside
+ * the sensor area and then slide their finger into the sensor area.
+ */
+ override fun onTouch(event: MotionEvent) {
+ // Don't forward touches if the shade has already started expanding.
+ if (transitionToFullShadeProgress != 0f) {
+ return
+ }
+ udfpsController.onTouch(event)
+ }
+ }
+
+ private val alternateAuthInterceptor: AlternateAuthInterceptor =
+ object : AlternateAuthInterceptor {
+ override fun showAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(true)
+ }
+
+ override fun hideAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(false)
+ }
+
+ override fun isShowingAlternateAuthBouncer(): Boolean {
+ return showingUdfpsBouncer
+ }
+
+ override fun requestUdfps(request: Boolean, color: Int) {
+ udfpsRequested = request
+ view.requestUdfps(request, color)
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun dump(pw: PrintWriter) {
+ pw.println(tag)
+ }
+ }
+
+ override val tag: String
+ get() = TAG
+
+ override fun onInit() {
+ super.onInit()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ }
+
+ init {
+ if (isModernBouncerEnabled) {
+ view.repeatWhenAttached {
+ // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+ // can make the view not visible; and we still want to listen for events
+ // that may make the view visible again.
+ repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ return scope.launch {
+ bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+ inputBouncerExpansion = bouncerExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+ }
+
+ public override fun onViewAttached() {
+ super.onViewAttached()
+ val dozeAmount = statusBarStateController.dozeAmount
+ lastDozeAmount = dozeAmount
+ stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
+ statusBarStateController.addCallback(stateListener)
+ udfpsRequested = false
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ keyguardStateController.addCallback(keyguardStateControllerCallback)
+ statusBarState = statusBarStateController.state
+ qsExpansion = keyguardViewManager.qsExpansion
+ keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ val bouncer = keyguardViewManager.bouncer
+ bouncer?.expansion?.let {
+ bouncerExpansionCallback.onExpansionChanged(it)
+ bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ updateBouncerHiddenAmount()
+ }
+ configurationController.addCallback(configurationListener)
+ shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
+ updateScaleFactor()
+ view.updatePadding()
+ updateAlpha()
+ updatePauseAuth()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = this
+ activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ }
+
+ override fun onViewDetached() {
+ super.onViewDetached()
+ faceDetectRunning = false
+ keyguardStateController.removeCallback(keyguardStateControllerCallback)
+ statusBarStateController.removeCallback(stateListener)
+ keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ configurationController.removeCallback(configurationListener)
+ shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
+ if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) {
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = null
+ }
+ activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+ keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ super.dump(pw, args)
+ pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
+ pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
+ pw.println("faceDetectRunning=$faceDetectRunning")
+ pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
+ pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
+ pw.println("qsExpansion=$qsExpansion")
+ pw.println("panelExpansionFraction=$panelExpansionFraction")
+ pw.println("unpausedAlpha=" + view.unpausedAlpha)
+ pw.println("udfpsRequestedByApp=$udfpsRequested")
+ pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
+ pw.println("lastDozeAmount=$lastDozeAmount")
+ if (isModernBouncerEnabled) {
+ pw.println("inputBouncerExpansion=$inputBouncerExpansion")
+ } else {
+ pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
+ }
+ view.dump(pw)
+ }
+
+ /**
+ * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
+ * @return whether the udfpsBouncer has been newly shown or hidden
+ */
+ private fun showUdfpsBouncer(show: Boolean): Boolean {
+ if (showingUdfpsBouncer == show) {
+ return false
+ }
+ val udfpsAffordanceWasNotShowing = shouldPauseAuth()
+ showingUdfpsBouncer = show
+ if (showingUdfpsBouncer) {
+ lastUdfpsBouncerShowTime = systemClock.uptimeMillis()
+ }
+ if (showingUdfpsBouncer) {
+ if (udfpsAffordanceWasNotShowing) {
+ view.animateInUdfpsBouncer(null)
+ }
+ if (keyguardStateController.isOccluded) {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
+ }
+ view.announceForAccessibility(
+ view.context.getString(R.string.accessibility_fingerprint_bouncer)
+ )
+ } else {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ }
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ return true
+ }
+
+ /**
+ * Returns true if the fingerprint manager is running but we want to temporarily pause
+ * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
+ * this can be overridden with the showBouncer method.
+ */
+ override fun shouldPauseAuth(): Boolean {
+ if (showingUdfpsBouncer) {
+ return false
+ }
+ if (
+ udfpsRequested &&
+ !notificationShadeVisible &&
+ !isInputBouncerFullyVisible() &&
+ keyguardStateController.isShowing
+ ) {
+ return false
+ }
+ if (launchTransitionFadingAway) {
+ return true
+ }
+
+ // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
+ // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
+ // delayed. However, we still animate in the UDFPS affordance with the
+ // mUnlockedScreenOffDozeAnimator.
+ if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
+ return true
+ }
+ if (isBouncerExpansionGreaterThan(.5f)) {
+ return true
+ }
+ return view.unpausedAlpha < 255 * .1
+ }
+
+ fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion >= bouncerExpansionThreshold
+ } else {
+ inputBouncerHiddenAmount < bouncerExpansionThreshold
+ }
+ }
+
+ fun isInputBouncerFullyVisible(): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion == 1f
+ } else {
+ keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+ }
+ }
+
+ override fun listenForTouchesOutsideView(): Boolean {
+ return true
+ }
+
+ override fun onTouchOutsideView() {
+ maybeShowInputBouncer()
+ }
+
+ /**
+ * If we were previously showing the udfps bouncer, hide it and instead show the regular
+ * (pin/pattern/password) bouncer.
+ *
+ * Does nothing if we weren't previously showing the UDFPS bouncer.
+ */
+ private fun maybeShowInputBouncer() {
+ if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
+ keyguardViewManager.showBouncer(true)
+ }
+ }
+
+ /**
+ * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the
+ * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer.
+ */
+ private fun hasUdfpsBouncerShownWithMinTime(): Boolean {
+ return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200
+ }
+
+ /**
+ * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
+ * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
+ * to expand the notification shade from the empty space in the middle of the lock screen.
+ */
+ fun setTransitionToFullShadeProgress(progress: Float) {
+ transitionToFullShadeProgress = progress
+ updateAlpha()
+ }
+
+ /**
+ * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
+ * based on the doze amount.
+ */
+ override fun updateAlpha() {
+ // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
+ // then the keyguard is occluded by some application - so instead use the input bouncer
+ // hidden amount to determine the fade.
+ val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
+ var alpha: Int =
+ if (showingUdfpsBouncer) 255
+ else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
+ if (!showingUdfpsBouncer) {
+ // swipe from top of the lockscreen to expand full QS:
+ alpha =
+ (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
+ .toInt()
+
+ // swipe from the middle (empty space) of lockscreen to expand the notification shade:
+ alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
+
+ // Fade out the icon if we are animating an activity launch over the lockscreen and the
+ // activity didn't request the UDFPS.
+ if (isLaunchingActivity && !udfpsRequested) {
+ alpha = (alpha * (1.0f - activityLaunchProgress)).toInt()
+ }
+
+ // Fade out alpha when a dialog is shown
+ // Fade in alpha when a dialog is hidden
+ alpha = (alpha * view.dialogSuggestedAlpha).toInt()
+ }
+ view.unpausedAlpha = alpha
+ }
+
+ private fun getInputBouncerHiddenAmt(): Float {
+ return if (isModernBouncerEnabled) {
+ 1f - inputBouncerExpansion
+ } else {
+ inputBouncerHiddenAmount
+ }
+ }
+
+ /** Update the scale factor based on the device's resolution. */
+ private fun updateScaleFactor() {
+ udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
+ }
+
+ private fun updateBouncerHiddenAmount() {
+ if (isModernBouncerEnabled) {
+ return
+ }
+ val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
+ if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+ inputBouncerHiddenAmount = 1f
+ } else if (keyguardViewManager.isBouncerShowing) {
+ // input bouncer is fully showing
+ inputBouncerHiddenAmount = 0f
+ }
+ }
+
+ companion object {
+ const val TAG = "UdfpsKeyguardViewController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 22dc94a..5850c95 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.os.Handler
import android.os.Looper
+import android.os.Trace
import android.os.UserHandle
import android.util.ArrayMap
import android.util.ArraySet
@@ -126,6 +127,7 @@
action,
userId,
{
+ Trace.beginSection("registerReceiver act=$action user=$userId")
context.registerReceiverAsUser(
this,
UserHandle.of(userId),
@@ -134,11 +136,14 @@
workerHandler,
flags
)
+ Trace.endSection()
logger.logContextReceiverRegistered(userId, flags, it)
},
{
try {
+ Trace.beginSection("unregisterReceiver act=$action user=$userId")
context.unregisterReceiver(this)
+ Trace.endSection()
logger.logContextReceiverUnregistered(userId, action)
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Trying to unregister unregistered receiver for user $userId, " +
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 500f280..2245d84 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -337,7 +337,8 @@
|| mTestHarness
|| mDataProvider.isJustUnlockedWithFace()
|| mDataProvider.isDocked()
- || mAccessibilityManager.isTouchExplorationEnabled();
+ || mAccessibilityManager.isTouchExplorationEnabled()
+ || mDataProvider.isA11yAction();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
new file mode 100644
index 0000000..63d57cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.classifier
+
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK
+import javax.inject.Inject
+
+/**
+ * Class that injects an artificial tap into the falsing collector.
+ *
+ * This is used for views that can be interacted with by A11y services and have falsing checks, as
+ * the gestures made by the A11y framework do not propagate motion events down the view hierarchy.
+ */
+class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
+ View.AccessibilityDelegate() {
+ override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ if (action == ACTION_CLICK) {
+ falsingCollector.onA11yAction()
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 858bac3..6670108 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -132,5 +132,8 @@
/** */
void updateFalseConfidence(FalsingClassifier.Result result);
+
+ /** Indicates an a11y action was made. */
+ void onA11yAction();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 0b7d6ab..cc25368 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -157,4 +157,8 @@
@Override
public void updateFalseConfidence(FalsingClassifier.Result result) {
}
+
+ @Override
+ public void onA11yAction() {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index da3d293..8bdef13 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -375,6 +375,15 @@
mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis());
}
+ @Override
+ public void onA11yAction() {
+ if (mPendingDownEvent != null) {
+ mPendingDownEvent.recycle();
+ mPendingDownEvent = null;
+ }
+ mFalsingDataProvider.onA11yAction();
+ }
+
private boolean shouldSessionBeActive() {
return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 3991a35..09ebeea 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -59,6 +59,7 @@
private MotionEvent mFirstRecentMotionEvent;
private MotionEvent mLastMotionEvent;
private boolean mJustUnlockedWithFace;
+ private boolean mA11YAction;
@Inject
public FalsingDataProvider(
@@ -124,6 +125,7 @@
mPriorMotionEvents = mRecentMotionEvents;
mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
+ mA11YAction = false;
}
/** Returns screen width in pixels. */
@@ -334,6 +336,17 @@
mGestureFinalizedListeners.remove(listener);
}
+ /** Return whether last gesture was an A11y action. */
+ public boolean isA11yAction() {
+ return mA11YAction;
+ }
+
+ /** Set whether last gesture was an A11y action. */
+ public void onA11yAction() {
+ completePriorGesture();
+ this.mA11YAction = true;
+ }
+
void onSessionStarted() {
mSessionListeners.forEach(SessionListener::onSessionStarted);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index bf7d716..6cb0e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -24,7 +24,6 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.SharedPreferences
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
@@ -59,7 +58,10 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -76,13 +78,14 @@
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
- @Main val sharedPreferences: SharedPreferences,
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val shadeController: ShadeController,
private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger,
- private val keyguardStateController: KeyguardStateController
+ private val keyguardStateController: KeyguardStateController,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
) : ControlsUiController {
companion object {
@@ -110,6 +113,12 @@
private lateinit var onDismiss: Runnable
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
+ private val sharedPreferences
+ get() = userFileManager.getSharedPreferences(
+ fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ mode = 0,
+ userId = userTracker.userId
+ )
private val collator = Collator.getInstance(context.resources.configuration.locales[0])
private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 344fb76..a677acf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -232,11 +232,13 @@
@JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
// 1100 - windowing
+ @JvmField
@Keep
val WM_ENABLE_SHELL_TRANSITIONS =
SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
/** b/170163464: animate bubbles expanded view collapse with home gesture */
+ @JvmField
@Keep
val BUBBLES_HOME_GESTURE =
SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
@@ -258,40 +260,50 @@
@Keep
val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+ @JvmField
@Keep
val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+ @JvmField
@Keep
val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+ @JvmField
@Keep
val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
+ @JvmField
@Keep
val SHOW_FLOATING_TASKS_AS_BUBBLES =
SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
+ @JvmField
@Keep
val ENABLE_FLING_TO_DISMISS_BUBBLE =
SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+ @JvmField
@Keep
val ENABLE_FLING_TO_DISMISS_PIP =
SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+ @JvmField
@Keep
val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
// 1200 - predictive back
+ @JvmField
@Keep
val WM_ENABLE_PREDICTIVE_BACK =
SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+ @JvmField
@Keep
val WM_ENABLE_PREDICTIVE_BACK_ANIM =
SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+ @JvmField
@Keep
val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
@@ -301,7 +313,7 @@
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
- @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true)
// TODO(b/254513155): Tracking Bug
@JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
@@ -315,7 +327,7 @@
val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true)
// 1700 - clipboard
- @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
// 1800 - shade container
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 99ae85d..80c6130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -18,6 +18,7 @@
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -45,4 +46,9 @@
fun dispatchBackKeyEventPreIme(): Boolean
fun showNextSecurityScreenOrFinish(): Boolean
fun resume()
+ fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?,
+ )
+ fun willDismissWithActions(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 543389e..0046256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -21,10 +21,9 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -41,9 +40,15 @@
/** Determines if we want to instantaneously show the bouncer instead of translating. */
private val _isScrimmed = MutableStateFlow(false)
val isScrimmed = _isScrimmed.asStateFlow()
- /** Set amount of how much of the bouncer is showing on the screen */
- private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
- val expansionAmount = _expansionAmount.asStateFlow()
+ /**
+ * Set how much of the panel is showing on the screen.
+ * ```
+ * 0f = panel fully hidden = bouncer fully showing
+ * 1f = panel fully showing = bouncer fully hidden
+ * ```
+ */
+ private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
+ val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _isVisible = MutableStateFlow(false)
val isVisible = _isVisible.asStateFlow()
private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
@@ -54,8 +59,6 @@
val hide = _hide.asStateFlow()
private val _startingToHide = MutableStateFlow(false)
val startingToHide = _startingToHide.asStateFlow()
- private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
- val onDismissAction = _onDismissAction.asStateFlow()
private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
val startingDisappearAnimation = _disappearAnimation.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
@@ -96,8 +99,8 @@
_isScrimmed.value = isScrimmed
}
- fun setExpansion(expansion: Float) {
- _expansionAmount.value = expansion
+ fun setPanelExpansion(panelExpansion: Float) {
+ _panelExpansionAmount.value = panelExpansion
}
fun setVisible(isVisible: Boolean) {
@@ -120,10 +123,6 @@
_startingToHide.value = startingToHide
}
- fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
- _onDismissAction.value = bouncerCallbackActionsModel
- }
-
fun setStartDisappearAnimation(runnable: Runnable?) {
_disappearAnimation.value = runnable
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 2af9318..dbb0352 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -30,7 +30,6 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -40,6 +39,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -77,7 +77,7 @@
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
errorMessage = repository.bouncerErrorMessage,
- expansionAmount = repository.expansionAmount.value
+ expansionAmount = repository.panelExpansionAmount.value
)
)
repository.setShowingSoon(false)
@@ -90,14 +90,22 @@
val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
val isVisible: Flow<Boolean> = repository.isVisible
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
- val expansionAmount: Flow<Float> = repository.expansionAmount
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
repository.startingDisappearAnimation.filterNotNull()
- val onDismissAction: Flow<BouncerCallbackActionsModel> =
- repository.onDismissAction.filterNotNull()
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition
+ val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+ /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+ val bouncerExpansion: Flow<Float> = //
+ combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
+ ->
+ if (isVisible) {
+ 1f - expansionAmount
+ } else {
+ 0f
+ }
+ }
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
@@ -128,7 +136,7 @@
Trace.beginSection("KeyguardBouncer#show")
repository.setScrimmed(isScrimmed)
if (isScrimmed) {
- setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
}
if (resumeBouncer) {
@@ -149,7 +157,6 @@
}
keyguardStateController.notifyBouncerShowing(true)
callbackInteractor.dispatchStartingToShow()
-
Trace.endSection()
}
@@ -168,7 +175,6 @@
keyguardStateController.notifyBouncerShowing(false /* showing */)
cancelShowRunnable()
repository.setShowingSoon(false)
- repository.setOnDismissAction(null)
repository.setVisible(false)
repository.setHide(true)
repository.setShow(null)
@@ -176,14 +182,17 @@
}
/**
- * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
- * where 0f => showing and 1f => hiding
+ * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+ * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+ * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+ * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+ * of 0f represents the bouncer fully showing.
*/
- fun setExpansion(expansion: Float) {
- val oldExpansion = repository.expansionAmount.value
+ fun setPanelExpansion(expansion: Float) {
+ val oldExpansion = repository.panelExpansionAmount.value
val expansionChanged = oldExpansion != expansion
if (repository.startingDisappearAnimation.value == null) {
- repository.setExpansion(expansion)
+ repository.setPanelExpansion(expansion)
}
if (
@@ -227,7 +236,7 @@
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
- repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
/** Update the resources of the views. */
@@ -282,7 +291,7 @@
/** Returns whether bouncer is fully showing. */
fun isFullyShowing(): Boolean {
return (repository.showingSoon.value || repository.isVisible.value) &&
- repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+ repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
repository.startingDisappearAnimation.value == null
}
@@ -294,8 +303,8 @@
/** If bouncer expansion is between 0f and 1f non-inclusive. */
fun isInTransit(): Boolean {
return repository.showingSoon.value ||
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
}
/** Return whether bouncer is animating away. */
@@ -305,7 +314,7 @@
/** Return whether bouncer will dismiss with actions */
fun willDismissWithAction(): Boolean {
- return repository.onDismissAction.value?.onDismissAction != null
+ return bouncerView.delegate?.willDismissWithActions() == true
}
/** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index df26014..a22958b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.collect
@@ -75,6 +76,17 @@
hostViewController.showPrimarySecurityScreen()
hostViewController.onResume()
}
+
+ override fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?
+ ) {
+ hostViewController.setOnDismissAction(onDismissAction, cancelAction)
+ }
+
+ override fun willDismissWithActions(): Boolean {
+ return hostViewController.hasDismissActions()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,15 +134,6 @@
}
launch {
- viewModel.setDismissAction.collect {
- hostViewController.setOnDismissAction(
- it.onDismissAction,
- it.cancelAction
- )
- }
- }
-
- launch {
viewModel.startDisappearAnimation.collect {
hostViewController.startDisappearAnimation(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9ad5211..9a92843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,7 +20,6 @@
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -38,7 +37,7 @@
private val interactor: BouncerInteractor,
) {
/** Observe on bouncer expansion amount. */
- val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+ val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
@@ -63,9 +62,6 @@
/** Observe whether bouncer is starting to hide. */
val startingToHide: Flow<Unit> = interactor.startingToHide
- /** Observe whether we want to set the dismiss action to the bouncer. */
- val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
-
/** Observe whether we want to start the disappear animation. */
val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c089511..85d15dc 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -85,7 +85,11 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -354,15 +358,6 @@
}
@Override
- public void onQuickStepStarted() {
- // Use navbar dragging as a signal to hide the rotate button
- mView.getRotationButtonController().setRotateSuggestionButtonState(false);
-
- // Hide the notifications panel when quick step starts
- mShadeController.collapsePanel(true /* animate */);
- }
-
- @Override
public void onPrioritizedRotation(@Surface.Rotation int rotation) {
mStartingQuickSwitchRotation = rotation;
if (rotation == -1) {
@@ -475,6 +470,24 @@
}
};
+ private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
+ new SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceReplaced(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+ };
+
@Inject
NavigationBar(
NavigationBarView navigationBarView,
@@ -680,7 +693,8 @@
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ mView.setComponents(
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController());
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -701,6 +715,8 @@
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
mOnComputeInternalInsetsListener);
+ mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+ notifyNavigationBarSurface();
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -779,6 +795,10 @@
mHandler.removeCallbacks(mEnableLayoutTransitions);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+ }
mFrame = null;
mOrientationHandle = null;
}
@@ -932,6 +952,12 @@
}
}
+ private void notifyNavigationBarSurface() {
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null;
+ mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
+ }
+
private int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
@@ -1257,8 +1283,8 @@
}
private void onVerticalChanged(boolean isVertical) {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- statusBar -> statusBar.setQsScrimEnabled(!isVertical));
+ mCentralSurfacesOptionalLazy.get().ifPresent(statusBar ->
+ statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical));
}
private boolean onNavigationTouch(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 622f5a2..83c2a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -412,10 +412,6 @@
logSomePresses(action, flags);
if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
- if (action == MotionEvent.ACTION_UP) {
- mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
- -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
- }
}
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 709467f..c319a82 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -287,8 +287,6 @@
mBackAnimation.setTriggerBack(true);
}
- mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
logGesture(mInRejectedExclusion
? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
@@ -300,8 +298,6 @@
mBackAnimation.setTriggerBack(false);
}
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
- mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
}
@Override
@@ -785,9 +781,6 @@
if (mExcludeRegion.contains(x, y)) {
if (withinRange) {
- // Log as exclusion only if it is in acceptable range in the first place.
- mOverviewProxyService.notifyBackAction(
- false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
// We don't have the end point for logging purposes.
mEndPoint.x = -1;
mEndPoint.y = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 703b95a..b5ceeae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -19,6 +19,7 @@
import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
@@ -33,6 +34,7 @@
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.util.LargeScreenUtils;
import java.util.Objects;
@@ -72,6 +74,7 @@
mMobileSignal = findViewById(R.id.mobile_signal);
mCarrierText = findViewById(R.id.qs_carrier_text);
mSpacer = findViewById(R.id.spacer);
+ updateResources();
}
/**
@@ -142,4 +145,20 @@
public void updateTextAppearance(@StyleRes int resId) {
FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId);
}
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateResources();
+ }
+
+ private void updateResources() {
+ boolean useLargeScreenHeader =
+ LargeScreenUtils.shouldUseLargeScreenShadeHeader(getResources());
+ mCarrierText.setMaxEms(
+ useLargeScreenHeader
+ ? Integer.MAX_VALUE
+ : getResources().getInteger(R.integer.qs_carrier_max_em)
+ );
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e9a6c25..1f92b12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,7 +140,7 @@
iv.setTag(R.id.qs_icon_tag, icon);
iv.setTag(R.id.qs_slash_tag, state.slash);
iv.setPadding(0, padding, 0, padding);
- if (d instanceof Animatable2) {
+ if (shouldAnimate && d instanceof Animatable2) {
Animatable2 a = (Animatable2) d;
a.start();
if (state.isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66be00d..46c4f41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,6 +64,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
@@ -149,6 +150,7 @@
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
+ private SurfaceControl mNavigationBarSurface;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
@@ -190,7 +192,8 @@
// TODO move this logic to message queue
mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
if (event.getActionMasked() == ACTION_DOWN) {
- centralSurfaces.getPanelController().startExpandLatencyTracking();
+ centralSurfaces.getNotificationPanelViewController()
+ .startExpandLatencyTracking();
}
mHandler.post(() -> {
int action = event.getActionMasked();
@@ -217,17 +220,15 @@
}
@Override
- public void onBackPressed() throws RemoteException {
+ public void onBackPressed() {
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-
- notifyBackAction(true, -1, -1, true, false);
});
}
@Override
- public void onImeSwitcherPressed() throws RemoteException {
+ public void onImeSwitcherPressed() {
// TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
// Launcher/Taskbar isn't display aware.
mContext.getSystemService(InputMethodManager.class)
@@ -316,12 +317,6 @@
}
@Override
- public void notifySwipeUpGestureStarted() {
- verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
- notifySwipeUpGestureStartedInternal());
- }
-
- @Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
notifyPrioritizedRotationInternal(rotation));
@@ -443,6 +438,7 @@
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
+ dispatchNavigationBarSurface();
// Force-update the systemui state flags
updateSystemUiStateFlags();
@@ -597,11 +593,18 @@
.commitUpdate(mContext.getDisplayId());
}
- public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) {
+ /**
+ * Called when the navigation bar surface is created or changed
+ */
+ public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
+ mNavigationBarSurface = navbarSurface;
+ dispatchNavigationBarSurface();
+ }
+
+ private void dispatchNavigationBarSurface() {
try {
if (mOverviewProxy != null) {
- mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+ mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify back action", e);
@@ -614,7 +617,7 @@
final NavigationBarView navBarView =
mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
final NotificationPanelViewController panelController =
- mCentralSurfacesOptionalLazy.get().get().getPanelController();
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController();
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
+ " navBarView=" + navBarView + " panelController=" + panelController);
@@ -800,24 +803,12 @@
}
}
- public void notifyQuickStepStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickStepStarted();
- }
- }
-
private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
}
}
- public void notifyQuickScrubStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickScrubStarted();
- }
- }
-
private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onAssistantProgress(progress);
@@ -836,12 +827,6 @@
}
}
- private void notifySwipeUpGestureStartedInternal() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
- }
- }
-
public void notifyAssistantVisibilityChanged(float visibility) {
try {
if (mOverviewProxy != null) {
@@ -1005,23 +990,20 @@
pw.print(" mWindowCornerRadius="); pw.println(mWindowCornerRadius);
pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
+ pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
mSysUiState.dump(pw, args);
}
public interface OverviewProxyListener {
default void onConnectionChanged(boolean isConnected) {}
- default void onQuickStepStarted() {}
- default void onSwipeUpGestureStarted() {}
default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
default void onOverviewShown(boolean fromHome) {}
- default void onQuickScrubStarted() {}
/** Notify the recents app (overview) is started by 3-button navigation. */
default void onToggleRecentApps() {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
default void onTaskbarAutohideSuspend(boolean suspend) {}
- default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
default void startAssistant(Bundle bundle) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 962de99..92f5c85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -33,7 +33,6 @@
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.shade.NotificationPanelView.DEBUG;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -42,7 +41,6 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -52,10 +50,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
@@ -103,7 +101,6 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
@@ -174,16 +171,13 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -209,7 +203,6 @@
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.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -256,28 +249,15 @@
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DRAWABLE = false;
-
private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false);
-
- /**
- * The parallax amount of the quick settings translation when dragging down the panel
- */
+ /** The parallax amount of the quick settings translation when dragging down the panel. */
private static final float QS_PARALLAX_AMOUNT = 0.175f;
-
- /**
- * Fling expanding QS.
- */
+ /** Fling expanding QS. */
public static final int FLING_EXPAND = 0;
-
- /**
- * Fling collapsing QS, potentially stopping when QS becomes QQS.
- */
+ /** Fling collapsing QS, potentially stopping when QS becomes QQS. */
private static final int FLING_COLLAPSE = 1;
-
- /**
- * Fling until QS is completely hidden.
- */
+ /** Fling until QS is completely hidden. */
private static final int FLING_HIDE = 2;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
@@ -291,6 +271,18 @@
* when flinging. A low value will make it that most flings will reach the maximum overshoot.
*/
private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
+ /**
+ * Maximum time before which we will expand the panel even for slow motions when getting a
+ * touch passed over from launcher.
+ */
+ private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
+ private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50;
+ private static final String COUNTER_PANEL_OPEN = "panel_open";
+ private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+ private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
+ private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
+ private static final Rect EMPTY_RECT = new Rect();
+
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
private final Resources mResources;
private final KeyguardStateController mKeyguardStateController;
@@ -299,49 +291,24 @@
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final SystemClock mSystemClock;
private final ShadeLogger mShadeLog;
-
private final DozeParameters mDozeParameters;
- private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
- private final Runnable mCollapseExpandAction = new CollapseExpandAction();
- private final OnOverscrollTopChangedListener
- mOnOverscrollTopChangedListener =
- new OnOverscrollTopChangedListener();
- private final OnEmptySpaceClickListener
- mOnEmptySpaceClickListener =
- new OnEmptySpaceClickListener();
- private final MyOnHeadsUpChangedListener
- mOnHeadsUpChangedListener =
- new MyOnHeadsUpChangedListener();
- private final HeightListener mHeightListener = new HeightListener();
+ private final Runnable mCollapseExpandAction = this::collapseOrExpand;
+ private final NsslOverscrollTopChangedListener mOnOverscrollTopChangedListener =
+ new NsslOverscrollTopChangedListener();
+ private final NotificationStackScrollLayout.OnEmptySpaceClickListener
+ mOnEmptySpaceClickListener = (x, y) -> onEmptySpaceClick();
+ private final ShadeHeadsUpChangedListener mOnHeadsUpChangedListener =
+ new ShadeHeadsUpChangedListener();
+ private final QS.HeightListener mHeightListener = this::onQsHeightChanged;
private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
private final SettingsChangeObserver mSettingsChangeObserver;
-
- @VisibleForTesting
- final StatusBarStateListener mStatusBarStateListener =
- new StatusBarStateListener();
+ private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
private final NotificationPanelView mView;
private final VibratorHelper mVibratorHelper;
private final MetricsLogger mMetricsLogger;
private final ConfigurationController mConfigurationController;
private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
- private final NotificationIconAreaController mNotificationIconAreaController;
-
- /**
- * Maximum time before which we will expand the panel even for slow motions when getting a
- * touch passed over from launcher.
- */
- private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
-
- private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50;
-
- private static final String COUNTER_PANEL_OPEN = "panel_open";
- private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
- private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
-
- private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
- private static final Rect EMPTY_RECT = new Rect();
-
private final InteractionJankMonitor mInteractionJankMonitor;
private final LayoutInflater mLayoutInflater;
private final FeatureFlags mFeatureFlags;
@@ -361,9 +328,7 @@
private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
private final FragmentService mFragmentService;
private final ScrimController mScrimController;
- private final PrivacyDotViewController mPrivacyDotViewController;
private final NotificationRemoteInputManager mRemoteInputManager;
-
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final ShadeTransitionController mShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
@@ -380,6 +345,11 @@
private final Interpolator mBounceInterpolator;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
+ private final QS.ScrollListener mQsScrollListener = this::onQsPanelScrollChanged;
+ private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
+ private final FragmentListener mQsFragmentListener = new QsFragmentListener();
+ private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
private boolean mIsLaunchAnimationRunning;
@@ -401,13 +371,11 @@
private float mKeyguardNotificationTopPadding;
/** Current max allowed keyguard notifications determined by measuring the panel. */
private int mMaxAllowedKeyguardNotifications;
-
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
- @VisibleForTesting
- QS mQs;
+ private QS mQs;
private FrameLayout mQsFrame;
private final QsFrameTranslateController mQsFrameTranslateController;
private KeyguardStatusViewController mKeyguardStatusViewController;
@@ -420,18 +388,11 @@
private float mQuickQsHeaderHeight;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-
private int mQsTrackingPointer;
private VelocityTracker mQsVelocityTracker;
private boolean mQsTracking;
-
- /**
- * If set, the ongoing touch gesture might both trigger the expansion in {@link
- * NotificationPanelView} and
- * the expansion for quick settings.
- */
+ /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
private boolean mConflictingQsExpansionGesture;
-
private boolean mPanelExpanded;
/**
@@ -486,11 +447,9 @@
* Used for split shade, two finger gesture as well as accessibility shortcut to QS.
* It needs to be set when movement starts as it resets at the end of expansion/collapse.
*/
- @VisibleForTesting
- boolean mQsExpandImmediate;
+ private boolean mQsExpandImmediate;
private boolean mTwoFingerQsExpandPossible;
private String mHeaderDebugInfo;
-
/**
* If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
* need to take this into account in our panel height calculation.
@@ -498,7 +457,6 @@
private boolean mQsAnimatorExpand;
private boolean mIsLaunchTransitionFinished;
private ValueAnimator mQsSizeChangeAnimator;
-
private boolean mQsScrimEnabled = true;
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
@@ -516,39 +474,27 @@
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
- private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
- setHeadsUpAnimatingAway(false);
- updatePanelExpansionAndVisibility();
- };
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
private int mAmbientIndicationBottomPadding;
+ /** Whether the notifications are displayed full width (no margins on the side). */
private boolean mIsFullWidth;
private boolean mBlockingExpansionForCurrentTouch;
+ // Following variables maintain state of events when input focus transfer may occur.
+ private boolean mExpectingSynthesizedDown;
+ private boolean mLastEventSynthesizedDown;
- /**
- * Following variables maintain state of events when input focus transfer may occur.
- */
- private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
- private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
-
- /**
- * Current dark amount that follows regular interpolation curve of animation.
- */
+ /** Current dark amount that follows regular interpolation curve of animation. */
private float mInterpolatedDarkAmount;
-
/**
* Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
* interpolation curve is different.
*/
private float mLinearDarkAmount;
-
private boolean mPulsing;
private boolean mHideIconsDuringLaunchAnimation = true;
private int mStackScrollerMeasuringPass;
- /**
- * Non-null if there's a heads-up notification that we're currently tracking the position of.
- */
+ /** Non-null if a heads-up notification's position is being tracked. */
@Nullable
private ExpandableNotificationRow mTrackedHeadsUpNotification;
private final ArrayList<Consumer<ExpandableNotificationRow>>
@@ -578,17 +524,19 @@
private final CommandQueue mCommandQueue;
private final UserManager mUserManager;
private final MediaDataManager mMediaDataManager;
+ @PanelState
+ private int mCurrentPanelState = STATE_CLOSED;
private final SysUiState mSysUiState;
-
private final NotificationShadeDepthController mDepthController;
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
- private KeyguardIndicationController mKeyguardIndicationController;
+ private final KeyguardIndicationController mKeyguardIndicationController;
private int mHeadsUpInset;
private boolean mHeadsUpPinnedMode;
private boolean mAllowExpandForSmallExpansion;
private Runnable mExpandAfterLayoutRunnable;
+ private Runnable mHideExpandedRunnable;
/**
* The padding between the start of notifications and the qs boundary on the lockscreen.
@@ -596,94 +544,51 @@
* qs boundary to be padded.
*/
private int mLockscreenNotificationQSPadding;
-
/**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade. This value can also go beyond 1.1 when we're overshooting!
*/
private float mTransitioningToFullShadeProgress;
-
/**
* Position of the qs bottom during the full shade transition. This is needed as the toppadding
* can change during state changes, which makes it much harder to do animations
*/
private int mTransitionToFullShadeQSPosition;
-
- /**
- * Distance that the full shade transition takes in order for qs to fully transition to the
- * shade.
- */
+ /** Distance a full shade transition takes in order for qs to fully transition to the shade. */
private int mDistanceForQSFullShadeTransition;
-
- /**
- * The translation amount for QS for the full shade transition
- */
+ /** The translation amount for QS for the full shade transition. */
private float mQsTranslationForFullShadeTransition;
- /**
- * The maximum overshoot allowed for the top padding for the full shade transition
- */
+ /** The maximum overshoot allowed for the top padding for the full shade transition. */
private int mMaxOverscrollAmountForPulse;
-
- /**
- * Should we animate the next bounds update
- */
+ /** Should we animate the next bounds update. */
private boolean mAnimateNextNotificationBounds;
- /**
- * The delay for the next bounds animation
- */
+ /** The delay for the next bounds animation. */
private long mNotificationBoundsAnimationDelay;
-
- /**
- * The duration of the notification bounds animation
- */
+ /** The duration of the notification bounds animation. */
private long mNotificationBoundsAnimationDuration;
- /**
- * Is this a collapse that started on the panel where we should allow the panel to intercept
- */
+ /** Whether a collapse that started on the panel should allow the panel to intercept. */
private boolean mIsPanelCollapseOnQQS;
-
private boolean mAnimatingQS;
-
- /**
- * The end bounds of a clipping animation.
- */
+ /** The end bounds of a clipping animation. */
private final Rect mQsClippingAnimationEndBounds = new Rect();
-
- /**
- * The animator for the qs clipping bounds.
- */
+ /** The animator for the qs clipping bounds. */
private ValueAnimator mQsClippingAnimation = null;
-
- /**
- * Is the current animator resetting the qs translation.
- */
+ /** Whether the current animator is resetting the qs translation. */
private boolean mIsQsTranslationResetAnimator;
- /**
- * Is the current animator resetting the pulse expansion after a drag down
- */
+ /** Whether the current animator is resetting the pulse expansion after a drag down. */
private boolean mIsPulseExpansionResetAnimator;
private final Rect mKeyguardStatusAreaClipBounds = new Rect();
private final Region mQsInterceptRegion = new Region();
-
- /**
- * The alpha of the views which only show on the keyguard but not in shade / shade locked
- */
+ /** Alpha of the views which only show on the keyguard but not in shade / shade locked. */
private float mKeyguardOnlyContentAlpha = 1.0f;
-
- /**
- * The translationY of the views which only show on the keyguard but in shade / shade locked.
- */
+ /** Y translation of the views that only show on the keyguard but in shade / shade locked. */
private int mKeyguardOnlyTransitionTranslationY = 0;
-
private float mUdfpsMaxYBurnInOffset;
-
- /**
- * Are we currently in gesture navigation
- */
+ /** Are we currently in gesture navigation. */
private boolean mIsGestureNavigation;
private int mOldLayoutDirection;
private NotificationShelfController mNotificationShelfController;
@@ -696,6 +601,7 @@
private int mQsClipTop;
private int mQsClipBottom;
private boolean mQsVisible;
+
private final ContentResolver mContentResolver;
private float mMinFraction;
@@ -714,55 +620,7 @@
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-
private final NPVCDownEventState.Buffer mLastDownEvents;
-
- private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
- () -> mKeyguardBottomArea.setVisibility(View.GONE);
-
- private final AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host,
- AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
- info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action
- == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
- || action
- == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
- mStatusBarKeyguardViewManager.showBouncer(true);
- return true;
- }
- return super.performAccessibilityAction(host, action, args);
- }
- };
-
- private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
- @Override
- public void onAdditionalTapRequired() {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
- mTapAgainViewController.show();
- } else {
- mKeyguardIndicationController.showTransientIndication(
- R.string.notification_tap_again);
- }
-
- if (!mStatusBarStateController.isDozing()) {
- mVibratorHelper.vibrate(
- Process.myUid(),
- mView.getContext().getPackageName(),
- ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
- "falsing-additional-tap-required",
- TOUCH_VIBRATION_ATTRIBUTES);
- }
- }
- };
-
private final CameraGestureHelper mCameraGestureHelper;
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -812,8 +670,20 @@
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
+
private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
+ private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
+ () -> mKeyguardBottomArea.setVisibility(View.GONE);
+ private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
+ setHeadsUpAnimatingAway(false);
+ updatePanelExpansionAndVisibility();
+ };
+ private final Runnable mMaybeHideExpandedRunnable = () -> {
+ if (getExpansionFraction() == 0.0f) {
+ getView().post(mHideExpandedRunnable);
+ }
+ };
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -848,7 +718,6 @@
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
- NotificationIconAreaController notificationIconAreaController,
AuthController authController,
ScrimController scrimController,
UserManager userManager,
@@ -857,7 +726,6 @@
AmbientState ambientState,
LockIconViewController lockIconViewController,
KeyguardMediaController keyguardMediaController,
- PrivacyDotViewController privacyDotViewController,
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
NavigationBarController navigationBarController,
@@ -875,6 +743,7 @@
SysUiState sysUiState,
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@@ -895,7 +764,6 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
- TouchHandler touchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -903,16 +771,16 @@
}
@Override
- public void onViewDetachedFromWindow(View v) {
- }
+ public void onViewDetachedFromWindow(View v) {}
});
- mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(touchHandler);
- mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+ mView.addOnLayoutChangeListener(new ShadeLayoutChangeListener());
+ mView.setOnTouchListener(createTouchHandler());
+ mView.setOnConfigurationChangedListener(config -> loadDimens());
mResources = mView.getResources();
mKeyguardStateController = keyguardStateController;
+ mKeyguardIndicationController = keyguardIndicationController;
mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mNotificationShadeWindowController = notificationShadeWindowController;
FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get();
@@ -945,7 +813,6 @@
mInteractionJankMonitor = interactionJankMonitor;
mSystemClock = systemClock;
mKeyguardMediaController = keyguardMediaController;
- mPrivacyDotViewController = privacyDotViewController;
mMetricsLogger = metricsLogger;
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
@@ -957,7 +824,6 @@
mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
- mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
mDepthController = notificationShadeDepthController;
@@ -1000,10 +866,7 @@
mShadeTransitionController = shadeTransitionController;
lockscreenShadeTransitionController.setNotificationPanelController(this);
shadeTransitionController.setNotificationPanelViewController(this);
- DynamicPrivacyControlListener
- dynamicPrivacyControlListener =
- new DynamicPrivacyControlListener();
- dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
+ dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged);
shadeExpansionStateManager.addStateListener(this::onPanelStateChanged);
@@ -1027,13 +890,14 @@
mIsGestureNavigation = QuickStepContract.isGesturalMode(currentMode);
mView.setBackgroundColor(Color.TRANSPARENT);
- OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
+ ShadeAttachStateChangeListener
+ onAttachStateChangeListener = new ShadeAttachStateChangeListener();
mView.addOnAttachStateChangeListener(onAttachStateChangeListener);
if (mView.isAttachedToWindow()) {
onAttachStateChangeListener.onViewAttachedToWindow(mView);
}
- mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener());
+ mView.setOnApplyWindowInsetsListener((v, insets) -> onApplyShadeWindowInsets(insets));
if (DEBUG_DRAWABLE) {
mView.getOverlay().add(new DebugDrawable());
@@ -1052,57 +916,68 @@
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@Override
public void onUnlockAnimationFinished() {
- // Make sure the clock is in the correct position after the unlock animation
- // so that it's not in the wrong place when we show the keyguard again.
- positionClockAndNotifications(true /* forceClockUpdate */);
+ unlockAnimationFinished();
}
@Override
public void onUnlockAnimationStarted(
boolean playingCannedAnimation,
boolean isWakeAndUnlock,
- long unlockAnimationStartDelay,
+ long startDelay,
long unlockAnimationDuration) {
- // Disable blurs while we're unlocking so that panel expansion does not
- // cause blurring. This will eventually be re-enabled by the panel view on
- // ACTION_UP, since the user's finger might still be down after a swipe to
- // unlock gesture, and we don't want that to cause blurring either.
- mDepthController.setBlursDisabledForUnlock(mTracking);
-
- if (playingCannedAnimation && !isWakeAndUnlock) {
- // Hide the panel so it's not in the way or the surface behind the
- // keyguard, which will be appearing. If we're wake and unlocking, the
- // lock screen is hidden instantly so should not be flung away.
- if (isTracking() || isFlinging()) {
- // Instant collpase the notification panel since the notification
- // panel is already in the middle animating
- onTrackingStopped(false);
- instantCollapse();
- } else {
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- // Translate up by 4%.
- .translationY(mView.getHeight() * -0.04f)
- // This start delay is to give us time to animate out before
- // the launcher icons animation starts, so use that as our
- // duration.
- .setDuration(unlockAnimationStartDelay)
- .setInterpolator(EMPHASIZED_ACCELERATE)
- .withEndAction(() -> {
- instantCollapse();
- mView.setAlpha(1f);
- mView.setTranslationY(0f);
- })
- .start();
- }
- }
+ unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
mCameraGestureHelper = cameraGestureHelper;
mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
}
+ private void unlockAnimationFinished() {
+ // Make sure the clock is in the correct position after the unlock animation
+ // so that it's not in the wrong place when we show the keyguard again.
+ positionClockAndNotifications(true /* forceClockUpdate */);
+ }
+
+ private void unlockAnimationStarted(
+ boolean playingCannedAnimation,
+ boolean isWakeAndUnlock,
+ long unlockAnimationStartDelay) {
+ // Disable blurs while we're unlocking so that panel expansion does not
+ // cause blurring. This will eventually be re-enabled by the panel view on
+ // ACTION_UP, since the user's finger might still be down after a swipe to
+ // unlock gesture, and we don't want that to cause blurring either.
+ mDepthController.setBlursDisabledForUnlock(mTracking);
+
+ if (playingCannedAnimation && !isWakeAndUnlock) {
+ // Hide the panel so it's not in the way or the surface behind the
+ // keyguard, which will be appearing. If we're wake and unlocking, the
+ // lock screen is hidden instantly so should not be flung away.
+ if (isTracking() || mIsFlinging) {
+ // Instant collapse the notification panel since the notification
+ // panel is already in the middle animating
+ onTrackingStopped(false);
+ instantCollapse();
+ } else {
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ // Translate up by 4%.
+ .translationY(mView.getHeight() * -0.04f)
+ // This start delay is to give us time to animate out before
+ // the launcher icons animation starts, so use that as our
+ // duration.
+ .setDuration(unlockAnimationStartDelay)
+ .setInterpolator(EMPHASIZED_ACCELERATE)
+ .withEndAction(() -> {
+ instantCollapse();
+ mView.setAlpha(1f);
+ mView.setTranslationY(0f);
+ })
+ .start();
+ }
+ }
+ }
+
@VisibleForTesting
void onFinishInflate() {
loadDimens();
@@ -1139,7 +1014,7 @@
R.id.notification_stack_scroller);
mNotificationStackScrollLayoutController.attach(stackScrollLayout);
mNotificationStackScrollLayoutController.setOnHeightChangedListener(
- mOnHeightChangedListener);
+ new NsslHeightChangedListener());
mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
mOnOverscrollTopChangedListener);
mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
@@ -1147,7 +1022,7 @@
mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
- mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+ setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
initBottomArea();
@@ -1260,11 +1135,6 @@
}
}
- private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
- // TODO: this can be injected.
- mCentralSurfaces = centralSurfaces;
- }
-
public void updateResources() {
mSplitShadeNotificationsScrimMarginBottom =
mResources.getDimensionPixelSize(
@@ -1350,7 +1220,7 @@
@VisibleForTesting
void reInflateViews() {
- if (DEBUG_LOGCAT) Log.d(TAG, "reInflateViews");
+ debugLog("reInflateViews");
// Re-inflate the status view group.
KeyguardStatusView keyguardStatusView =
mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
@@ -1396,7 +1266,7 @@
int index = mView.indexOfChild(mKeyguardBottomArea);
mView.removeView(mKeyguardBottomArea);
KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
- mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView();
+ setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
mKeyguardBottomArea.initFrom(oldBottomArea);
mView.addView(mKeyguardBottomArea, index);
initBottomArea();
@@ -1429,6 +1299,11 @@
mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
}
+ @VisibleForTesting
+ void setQs(QS qs) {
+ mQs = qs;
+ }
+
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
mKeyguardMediaController.attachSplitShadeContainer(container);
}
@@ -1443,12 +1318,7 @@
}
@VisibleForTesting
- boolean getClosing() {
- return mClosing;
- }
-
- @VisibleForTesting
- boolean getIsFlinging() {
+ boolean isFlinging() {
return mIsFlinging;
}
@@ -1475,8 +1345,8 @@
return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
}
- public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
- mKeyguardIndicationController = indicationController;
+ private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
+ mKeyguardBottomArea = keyguardBottomArea;
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
}
@@ -1923,13 +1793,13 @@
setQsExpandImmediate(true);
setShowShelfOnly(true);
}
- if (DEBUG) this.logf("collapse: " + this);
+ debugLog("collapse: %s", this);
if (canPanelBeCollapsed()) {
cancelHeightAnimator();
notifyExpandingStarted();
// Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- setIsClosing(true);
+ setClosing(true);
if (delayed) {
mNextCollapseSpeedUpFactor = speedUpFactor;
this.mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -1939,13 +1809,19 @@
}
}
- private void setQsExpandImmediate(boolean expandImmediate) {
+ @VisibleForTesting
+ void setQsExpandImmediate(boolean expandImmediate) {
if (expandImmediate != mQsExpandImmediate) {
mQsExpandImmediate = expandImmediate;
mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
}
}
+ @VisibleForTesting
+ boolean isQsExpandImmediate() {
+ return mQsExpandImmediate;
+ }
+
private void setShowShelfOnly(boolean shelfOnly) {
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
shelfOnly && !mSplitShadeEnabled);
@@ -2032,12 +1908,12 @@
}
}
- public void fling(float vel, boolean expand) {
+ private void fling(float vel) {
GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
if (gr != null) {
gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
}
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+ fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false);
}
@VisibleForTesting
@@ -2124,7 +2000,7 @@
@Override
public void onAnimationEnd(Animator animation) {
if (shouldSpringBack && !mCancelled) {
- // After the shade is flinged open to an overscrolled state, spring back
+ // After the shade is flung open to an overscrolled state, spring back
// the shade by reducing section padding to 0.
springBack();
} else {
@@ -2154,7 +2030,7 @@
}
private boolean onQsIntercept(MotionEvent event) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept");
+ debugLog("onQsIntercept");
int pointerIndex = event.findPointerIndex(mQsTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -2215,7 +2091,7 @@
if ((h > touchSlop || (h < -touchSlop && mQsExpanded))
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
+ debugLog("onQsIntercept - start tracking expansion");
mView.getParent().requestDisallowInterceptTouchEvent(true);
mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
mQsTracking = true;
@@ -2274,7 +2150,7 @@
private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
- mDozingOnDown = isDozing();
+ mDozingOnDown = mDozing;
mDownX = event.getX();
mDownY = event.getY();
mCollapsedOnDown = isFullyCollapsed();
@@ -2324,7 +2200,7 @@
float vel = getCurrentQSVelocity();
boolean expandsQs = flingExpandsQs(vel);
if (expandsQs) {
- if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) {
+ if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
expandsQs = false;
} else {
logQsSwipeDown(y);
@@ -2363,9 +2239,9 @@
}
}
- private boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+ private boolean isFalseTouch() {
if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch(interactionType);
+ return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS);
}
return !mQsTouchAboveFalsingThreshold;
}
@@ -2491,7 +2367,7 @@
private void handleQsDown(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
event.getX(), event.getY(), -1)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown");
+ debugLog("handleQsDown");
mFalsingCollector.onQsDown();
mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
mQsTracking = true;
@@ -2505,9 +2381,7 @@
}
}
- /**
- * Input focus transfer is about to happen.
- */
+ /** Input focus transfer is about to happen. */
public void startWaitingForOpenPanelGesture() {
if (!isFullyCollapsed()) {
return;
@@ -2539,7 +2413,7 @@
} else {
// Window never will receive touch events that typically trigger haptic on open.
maybeVibrateOnOpening(false /* openingWithTouch */);
- fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
+ fling(velocity > 1f ? 1000f * velocity : 0 /* expand */);
}
onTrackingStopped(false);
}
@@ -2613,7 +2487,7 @@
break;
case MotionEvent.ACTION_MOVE:
- if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move");
+ debugLog("onQSTouch move");
mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
setQsExpansionHeight(h + mInitialHeightOnTouch);
if (h >= getFalsingThreshold()) {
@@ -2690,6 +2564,9 @@
navigationBarView.onStatusBarPanelStateChanged();
}
mShadeExpansionStateManager.onQsExpansionChanged(expanded);
+ mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
+ mQsMinExpansionHeight, mQsMaxExpansionHeight, mStackScrollerOverscrolling,
+ mDozing, mQsAnimatorExpand, mAnimatingQS);
}
}
@@ -2901,7 +2778,7 @@
}
private int calculateLeftQsClippingBound() {
- if (isFullWidth()) {
+ if (mIsFullWidth) {
// left bounds can ignore insets, it should always reach the edge of the screen
return 0;
} else {
@@ -2910,7 +2787,7 @@
}
private int calculateRightQsClippingBound() {
- if (isFullWidth()) {
+ if (mIsFullWidth) {
return getView().getRight() + mDisplayRightInset;
} else {
return mNotificationStackScrollLayoutController.getRight();
@@ -2978,7 +2855,7 @@
// Fancy clipping for quick settings
int radius = mScrimCornerRadius;
boolean clipStatusView = false;
- if (isFullWidth()) {
+ if (mIsFullWidth) {
// The padding on this area is large enough that we can use a cheaper clipping strategy
mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
clipStatusView = qsVisible;
@@ -3128,10 +3005,7 @@
}
}
- /**
- * @return the topPadding of notifications when on keyguard not respecting quick settings
- * expansion
- */
+ /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
private int getKeyguardNotificationStaticPadding() {
if (!mKeyguardShowing) {
return 0;
@@ -3163,7 +3037,7 @@
* shade. 0.0f means we're not transitioning yet.
*/
public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
- if (animate && isFullWidth()) {
+ if (animate && mIsFullWidth) {
animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
delay);
mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f;
@@ -3212,10 +3086,7 @@
updateQsExpansion();
}
- /**
- * Notify the panel that the pulse expansion has finished and that we're going to the full
- * shade
- */
+ /** Called when pulse expansion has finished and this is going to the full shade. */
public void onPulseExpansionFinished() {
animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0);
mIsPulseExpansionResetAnimator = true;
@@ -3270,9 +3141,7 @@
}
}
- /**
- * @see #flingSettings(float, int, Runnable, boolean)
- */
+ /** @see #flingSettings(float, int, Runnable, boolean) */
public void flingSettings(float vel, int type) {
flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */);
}
@@ -3405,7 +3274,8 @@
return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
}
- public int getMaxPanelHeight() {
+ @VisibleForTesting
+ int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
&& mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
@@ -3439,13 +3309,20 @@
}
private void onHeightUpdated(float expandedHeight) {
+ if (expandedHeight <= 0) {
+ mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.",
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ } else if (isFullyExpanded()) {
+ mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ }
if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
// Updating the clock position will set the top padding which might
// trigger a new panel height and re-position the clock.
// This is a circular dependency and should be avoided, otherwise we'll have
// a stack overflow.
if (mStackScrollerMeasuringPass > 2) {
- if (DEBUG_LOGCAT) Log.d(TAG, "Unstable notification panel height. Aborting.");
+ debugLog("Unstable notification panel height. Aborting.");
} else {
positionClockAndNotifications();
}
@@ -3581,9 +3458,7 @@
return alpha;
}
- /**
- * Hides the header when notifications are colliding with it.
- */
+ /** Hides the header when notifications are colliding with it. */
private void updateHeader() {
if (mBarState == KEYGUARD) {
mKeyguardStatusBarViewController.updateViewState();
@@ -3726,7 +3601,7 @@
if (mAnimateAfterExpanding) {
notifyExpandingStarted();
beginJankMonitoring();
- fling(0, true /* expand */);
+ fling(0 /* expand */);
} else {
setExpandedFraction(1f);
}
@@ -3763,6 +3638,24 @@
}
+ private void falsingAdditionalTapRequired() {
+ if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
+ mTapAgainViewController.show();
+ } else {
+ mKeyguardIndicationController.showTransientIndication(
+ R.string.notification_tap_again);
+ }
+
+ if (!mStatusBarStateController.isDozing()) {
+ mVibratorHelper.vibrate(
+ Process.myUid(),
+ mView.getContext().getPackageName(),
+ ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
+ "falsing-additional-tap-required",
+ TOUCH_VIBRATION_ATTRIBUTES);
+ }
+ }
+
private void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
endClosing();
@@ -3797,7 +3690,7 @@
private void updateMaxHeadsUpTranslation() {
mNotificationStackScrollLayoutController.setHeadsUpBoundaries(
- getHeight(), mNavigationBarBottomHeight);
+ mView.getHeight(), mNavigationBarBottomHeight);
}
@VisibleForTesting
@@ -3842,7 +3735,8 @@
|| !isTracking());
}
- public int getMaxPanelTransitionDistance() {
+ @VisibleForTesting
+ int getMaxPanelTransitionDistance() {
// Traditionally the value is based on the number of notifications. On split-shade, we want
// the required distance to be a specific and constant value, to make sure the expansion
// motion has the expected speed. We also only want this on non-lockscreen for now.
@@ -3898,10 +3792,9 @@
}
@VisibleForTesting
- void setIsClosing(boolean isClosing) {
- boolean wasClosing = isClosing();
- mClosing = isClosing;
- if (wasClosing != isClosing) {
+ void setClosing(boolean isClosing) {
+ if (mClosing != isClosing) {
+ mClosing = isClosing;
mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
}
mAmbientState.setIsClosing(isClosing);
@@ -3914,10 +3807,6 @@
}
}
- public boolean isDozing() {
- return mDozing;
- }
-
public void setQsScrimEnabled(boolean qsScrimEnabled) {
boolean changed = mQsScrimEnabled != qsScrimEnabled;
mQsScrimEnabled = qsScrimEnabled;
@@ -3930,7 +3819,7 @@
mKeyguardStatusViewController.dozeTimeTick();
}
- private boolean onMiddleClicked() {
+ private void onMiddleClicked() {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
@@ -3952,14 +3841,12 @@
startUnlockHintAnimation();
}
}
- return true;
+ break;
case StatusBarState.SHADE_LOCKED:
if (!mQsExpanded) {
mStatusBarStateController.setState(KEYGUARD);
}
- return true;
- default:
- return true;
+ break;
}
}
@@ -3995,6 +3882,7 @@
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
NotificationPanelViewController.this);
@@ -4033,17 +3921,9 @@
updateStatusBarIcons();
}
- /**
- * @return whether the notifications are displayed full width and don't have any margins on
- * the side.
- */
- public boolean isFullWidth() {
- return mIsFullWidth;
- }
-
private void updateStatusBarIcons() {
boolean showIconsWhenExpanded =
- (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+ (isPanelVisibleBecauseOfHeadsUp() || mIsFullWidth)
&& getExpandedHeight() < getOpeningHeight();
if (showIconsWhenExpanded && isOnKeyguard()) {
showIconsWhenExpanded = false;
@@ -4058,10 +3938,7 @@
return mBarState == KEYGUARD;
}
- /**
- * Called when heads-up notification is being dragged up or down to indicate what's the starting
- * height for shade motion
- */
+ /** Called when a HUN is dragged up or down to indicate the starting height for shade motion. */
public void setHeadsUpDraggingStartingHeight(int startHeight) {
mHeadsUpStartHeight = startHeight;
float scrimMinFraction;
@@ -4115,25 +3992,18 @@
setLaunchingAffordance(false);
}
- /**
- * Set whether we are currently launching an affordance. This is currently only set when
- * launched via a camera gesture.
- */
+ /** Set whether we are currently launching an affordance (i.e. camera gesture). */
private void setLaunchingAffordance(boolean launchingAffordance) {
mLaunchingAffordance = launchingAffordance;
mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
}
- /**
- * Return true when a bottom affordance is launching an occluded activity with a splash screen.
- */
+ /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */
public boolean isLaunchingAffordanceWithPreview() {
return mLaunchingAffordance;
}
- /**
- * Whether the camera application can be launched for the camera launch gesture.
- */
+ /** Whether the camera application can be launched by the camera launch gesture. */
public boolean canCameraGestureBeLaunched() {
return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
}
@@ -4146,22 +4016,19 @@
&& mHeadsUpAppearanceController.shouldBeVisible()) {
return false;
}
- return !isFullWidth() || !mShowIconsWhenExpanded;
+ return !mIsFullWidth || !mShowIconsWhenExpanded;
}
- public final QS.ScrollListener mScrollListener = new QS.ScrollListener() {
- @Override
- public void onQsPanelScrollChanged(int scrollY) {
- mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
- if (scrollY > 0 && !mQsFullyExpanded) {
- if (DEBUG_LOGCAT) Log.d(TAG, "Scrolling while not expanded. Forcing expand");
- // If we are scrolling QS, we should be fully expanded.
- expandWithQs();
- }
+ private void onQsPanelScrollChanged(int scrollY) {
+ mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
+ if (scrollY > 0 && !mQsFullyExpanded) {
+ debugLog("Scrolling while not expanded. Forcing expand");
+ // If we are scrolling QS, we should be fully expanded.
+ expandWithQs();
}
- };
+ }
- private final FragmentListener mFragmentListener = new FragmentListener() {
+ private final class QsFragmentListener implements FragmentListener {
@Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
@@ -4178,7 +4045,7 @@
final int height = bottom - top;
final int oldHeight = oldBottom - oldTop;
if (height != oldHeight) {
- mHeightListener.onQsHeightChanged();
+ onQsHeightChanged();
}
});
mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
@@ -4191,7 +4058,7 @@
mLockscreenShadeTransitionController.setQS(mQs);
mShadeTransitionController.setQs(mQs);
mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
- mQs.setScrollListener(mScrollListener);
+ mQs.setScrollListener(mQsScrollListener);
updateQsExpansion();
}
@@ -4204,7 +4071,7 @@
mQs = null;
}
}
- };
+ }
private void animateNextNotificationBounds(long duration, long delay) {
mAnimateNextNotificationBounds = true;
@@ -4294,13 +4161,7 @@
mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
}
- /**
- * TODO: this should be removed.
- * It's not correct to pass this view forward because other classes will end up adding
- * children to it. Theme will be out of sync.
- *
- * @return bottom area view
- */
+ //TODO(b/254875405): this should be removed.
public KeyguardBottomAreaView getKeyguardBottomAreaView() {
return mKeyguardBottomArea;
}
@@ -4329,11 +4190,8 @@
mHeadsUpAppearanceController = headsUpAppearanceController;
}
- /**
- * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
- * security view of the bouncer.
- */
- public void onBouncerPreHideAnimation() {
+ /** Called before animating Keyguard dismissal, i.e. the animation dismissing the bouncer. */
+ public void startBouncerPreHideAnimation() {
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
mBarState,
@@ -4350,9 +4208,7 @@
}
}
- /**
- * Updates the views to the initial state for the fold to AOD animation
- */
+ /** Updates the views to the initial state for the fold to AOD animation. */
public void prepareFoldToAodAnimation() {
// Force show AOD UI even if we are not locked
showAodUi();
@@ -4394,14 +4250,11 @@
public void onAnimationEnd(Animator animation) {
endAction.run();
}
- }).setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
- }).start();
+ }).setUpdateListener(anim -> mKeyguardStatusViewController.animateFoldToAod(
+ anim.getAnimatedFraction())).start();
}
- /**
- * Cancels fold to AOD transition and resets view state
- */
+ /** Cancels fold to AOD transition and resets view state. */
public void cancelFoldToAodAnimation() {
cancelAnimation();
resetAlpha();
@@ -4445,42 +4298,11 @@
}
}
- public boolean hasActiveClearableNotifications() {
- return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
- }
public RemoteInputController.Delegate createRemoteInputDelegate() {
return mNotificationStackScrollLayoutController.createDelegate();
}
- /**
- * Updates the notification views' sections and status bar icons. This is
- * triggered by the NotificationPresenter whenever there are changes to the underlying
- * notification data being displayed. In the new notification pipeline, this is handled in
- * {@link ShadeViewManager}.
- */
- public void updateNotificationViews() {
- mNotificationStackScrollLayoutController.updateFooter();
-
- mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
- }
-
- private List<ListEntry> createVisibleEntriesList() {
- List<ListEntry> entries = new ArrayList<>(
- mNotificationStackScrollLayoutController.getChildCount());
- for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
- View view = mNotificationStackScrollLayoutController.getChildAt(i);
- if (view instanceof ExpandableNotificationRow) {
- entries.add(((ExpandableNotificationRow) view).getEntry());
- }
- }
- return entries;
- }
-
- public void onUpdateRowStates() {
- mNotificationStackScrollLayoutController.onUpdateRowStates();
- }
-
public boolean hasPulsingNotifications() {
return mNotificationListContainer.hasPulsingNotifications();
}
@@ -4497,16 +4319,6 @@
mNotificationStackScrollLayoutController.runAfterAnimationFinished(r);
}
- private Runnable mHideExpandedRunnable;
- private final Runnable mMaybeHideExpandedRunnable = new Runnable() {
- @Override
- public void run() {
- if (getExpansionFraction() == 0.0f) {
- mView.post(mHideExpandedRunnable);
- }
- }
- };
-
/**
* Initialize objects instead of injecting to avoid circular dependencies.
*
@@ -4516,7 +4328,9 @@
CentralSurfaces centralSurfaces,
Runnable hideExpandedRunnable,
NotificationShelfController notificationShelfController) {
- setCentralSurfaces(centralSurfaces);
+ // TODO(b/254859580): this can be injected.
+ mCentralSurfaces = centralSurfaces;
+
mHideExpandedRunnable = hideExpandedRunnable;
mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
mNotificationShelfController = notificationShelfController;
@@ -4524,10 +4338,6 @@
updateMaxDisplayedNotifications(true);
}
- public void setAlpha(float alpha) {
- mView.setAlpha(alpha);
- }
-
public void resetTranslation() {
mView.setTranslationX(0f);
}
@@ -4546,22 +4356,14 @@
ViewGroupFadeHelper.reset(mView);
}
- public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
}
- public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
- public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() {
- return mOnHeadsUpChangedListener;
- }
-
- public int getHeight() {
- return mView.getHeight();
- }
-
public void setHeaderDebugInfo(String text) {
if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
}
@@ -4570,10 +4372,6 @@
mConfigurationListener.onThemeChanged();
}
- private OnLayoutChangeListener createLayoutChangeListener() {
- return new OnLayoutChangeListener();
- }
-
@VisibleForTesting
TouchHandler createTouchHandler() {
return new TouchHandler();
@@ -4628,10 +4426,6 @@
}
};
- private OnConfigurationChangedListener createOnConfigurationChangedListener() {
- return new OnConfigurationChangedListener();
- }
-
public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
return mNotificationStackScrollLayoutController;
}
@@ -4672,13 +4466,7 @@
);
}
- private void unregisterSettingsChangeListener() {
- mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
- }
-
- /**
- * Updates notification panel-specific flags on {@link SysUiState}.
- */
+ /** Updates notification panel-specific flags on {@link SysUiState}. */
public void updateSystemUiStateFlags() {
if (SysUiState.DEBUG) {
Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
@@ -4690,8 +4478,10 @@
.commitUpdate(mDisplayId);
}
- private void logf(String fmt, Object... args) {
- Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ private void debugLog(String fmt, Object... args) {
+ if (DEBUG_LOGCAT) {
+ Log.d(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ }
}
@VisibleForTesting
@@ -4755,9 +4545,8 @@
* Maybe vibrate as panel is opened.
*
* @param openingWithTouch Whether the panel is being opened with touch. If the panel is
- * instead
- * being opened programmatically (such as by the open panel gesture), we
- * always play haptic.
+ * instead being opened programmatically (such as by the open panel
+ * gesture), we always play haptic.
*/
private void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening) {
@@ -4854,10 +4643,10 @@
mUpdateFlingVelocity = vel;
}
} else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
- boolean expands = onEmptySpaceClick();
- onTrackingStopped(expands);
+ onEmptySpaceClick();
+ onTrackingStopped(true);
}
mVelocityTracker.clear();
}
@@ -4869,7 +4658,7 @@
private void endClosing() {
if (mClosing) {
- setIsClosing(false);
+ setClosing(false);
onClosingFinished();
}
}
@@ -4904,7 +4693,7 @@
boolean expandBecauseOfFalsing) {
float target = expand ? getMaxPanelHeight() : 0;
if (!expand) {
- setIsClosing(true);
+ setClosing(true);
}
flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
@@ -4939,13 +4728,9 @@
animator.start();
}
- public String getName() {
- return mViewName;
- }
-
@VisibleForTesting
void setExpandedHeight(float height) {
- if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+ debugLog("setExpandedHeight(%.1f)", height);
setExpandedHeightInternal(height);
}
@@ -5037,7 +4822,7 @@
return mExpandedHeight;
}
- public float getExpandedFraction() {
+ private float getExpandedFraction() {
return mExpandedFraction;
}
@@ -5053,10 +4838,6 @@
return mClosing || mIsLaunchAnimationRunning;
}
- public boolean isFlinging() {
- return mIsFlinging;
- }
-
public boolean isTracking() {
return mTracking;
}
@@ -5203,8 +4984,7 @@
*/
public void updatePanelExpansionAndVisibility() {
mShadeExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(),
- mTracking, mExpansionDragDownAmountPx);
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
@@ -5217,16 +4997,11 @@
&& !mIsSpringBackAnimation;
}
- /**
- * Gets called when the user performs a click anywhere in the empty area of the panel.
- *
- * @return whether the panel will be expanded after the action performed by this method
- */
- private boolean onEmptySpaceClick() {
- if (mHintAnimationRunning) {
- return true;
+ /** Called when the user performs a click anywhere in the empty area of the panel. */
+ private void onEmptySpaceClick() {
+ if (!mHintAnimationRunning) {
+ onMiddleClicked();
}
- return onMiddleClicked();
}
@VisibleForTesting
@@ -5243,7 +5018,7 @@
/** Returns the NotificationPanelView. */
public ViewGroup getView() {
- // TODO: remove this method, or at least reduce references to it.
+ // TODO(b/254878364): remove this method, or at least reduce references to it.
return mView;
}
@@ -5283,12 +5058,11 @@
return mShadeExpansionStateManager;
}
- private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
+ private final class NsslHeightChangedListener implements
+ ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
-
- // Block update if we are in quick settings and just the top padding changed
- // (i.e. view == null).
+ // Block update if we are in QS and just the top padding changed (i.e. view == null).
if (view == null && mQsExpanded) {
return;
}
@@ -5312,26 +5086,22 @@
}
@Override
- public void onReset(ExpandableView view) {
+ public void onReset(ExpandableView view) {}
+ }
+
+ private void collapseOrExpand() {
+ onQsExpansionStarted();
+ if (mQsExpanded) {
+ flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
+ true /* isClick */);
+ } else if (isQsExpansionEnabled()) {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+ flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
+ true /* isClick */);
}
}
- private class CollapseExpandAction implements Runnable {
- @Override
- public void run() {
- onQsExpansionStarted();
- if (mQsExpanded) {
- flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
- true /* isClick */);
- } else if (isQsExpansionEnabled()) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
- flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
- true /* isClick */);
- }
- }
- }
-
- private class OnOverscrollTopChangedListener implements
+ private final class NsslOverscrollTopChangedListener implements
NotificationStackScrollLayout.OnOverscrollTopChangedListener {
@Override
public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
@@ -5375,27 +5145,16 @@
}
}
- private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener {
- @Override
- public void onDynamicPrivacyChanged() {
- // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
- // of sync with the notification panel.
- if (mLinearDarkAmount != 0) {
- return;
- }
- mAnimateNextPositionUpdate = true;
+ private void onDynamicPrivacyChanged() {
+ // Do not request animation when pulsing or waking up, otherwise the clock will be out
+ // of sync with the notification panel.
+ if (mLinearDarkAmount != 0) {
+ return;
}
+ mAnimateNextPositionUpdate = true;
}
- private class OnEmptySpaceClickListener implements
- NotificationStackScrollLayout.OnEmptySpaceClickListener {
- @Override
- public void onEmptySpaceClicked(float x, float y) {
- onEmptySpaceClick();
- }
- }
-
- private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
+ private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
@Override
public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
if (inPinnedMode) {
@@ -5435,32 +5194,31 @@
}
}
- private class HeightListener implements QS.HeightListener {
- public void onQsHeightChanged() {
- mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
- if (mQsExpanded && mQsFullyExpanded) {
- mQsExpansionHeight = mQsMaxExpansionHeight;
- requestScrollerTopPaddingUpdate(false /* animate */);
- updateExpandedHeightToMaxHeight();
- }
- if (mAccessibilityManager.isEnabled()) {
- mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- }
- mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
+ private void onQsHeightChanged() {
+ mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
+ if (mQsExpanded && mQsFullyExpanded) {
+ mQsExpansionHeight = mQsMaxExpansionHeight;
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ updateExpandedHeightToMaxHeight();
}
+ if (mAccessibilityManager.isEnabled()) {
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
+ mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
}
- private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
+ private final class ConfigurationListener implements
+ ConfigurationController.ConfigurationListener {
@Override
public void onThemeChanged() {
- if (DEBUG_LOGCAT) Log.d(TAG, "onThemeChanged");
+ debugLog("onThemeChanged");
reInflateViews();
}
@Override
public void onSmallestScreenWidthChanged() {
Trace.beginSection("onSmallestScreenWidthChanged");
- if (DEBUG_LOGCAT) Log.d(TAG, "onSmallestScreenWidthChanged");
+ debugLog("onSmallestScreenWidthChanged");
// Can affect multi-user switcher visibility as it depends on screen size by default:
// it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
@@ -5477,27 +5235,26 @@
@Override
public void onDensityOrFontScaleChanged() {
- if (DEBUG_LOGCAT) Log.d(TAG, "onDensityOrFontScaleChanged");
+ debugLog("onDensityOrFontScaleChanged");
reInflateViews();
}
}
- private class SettingsChangeObserver extends ContentObserver {
-
+ private final class SettingsChangeObserver extends ContentObserver {
SettingsChangeObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onSettingsChanged");
+ debugLog("onSettingsChanged");
// Can affect multi-user switcher visibility
reInflateViews();
}
}
- private class StatusBarStateListener implements StateListener {
+ private final class StatusBarStateListener implements StateListener {
@Override
public void onStateChanged(int statusBarState) {
boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
@@ -5653,21 +5410,19 @@
setExpandedFraction(1f);
}
- /**
- * Sets the overstretch amount in raw pixels when dragging down.
- */
- public void setOverStrechAmount(float amount) {
+ /** Sets the overstretch amount in raw pixels when dragging down. */
+ public void setOverStretchAmount(float amount) {
float progress = amount / mView.getHeight();
- float overstretch = Interpolators.getOvershootInterpolation(progress);
- mOverStretchAmount = overstretch * mMaxOverscrollAmountForPulse;
+ float overStretch = Interpolators.getOvershootInterpolation(progress);
+ mOverStretchAmount = overStretch * mMaxOverscrollAmountForPulse;
positionClockAndNotifications(true /* forceUpdate */);
}
- private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
+ private final class ShadeAttachStateChangeListener implements View.OnAttachStateChangeListener {
@Override
public void onViewAttachedToWindow(View v) {
mFragmentService.getFragmentHostManager(mView)
- .addTagListener(QS.TAG, mFragmentListener);
+ .addTagListener(QS.TAG, mQsFragmentListener);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
mConfigurationController.addCallback(mConfigurationListener);
@@ -5682,16 +5437,16 @@
@Override
public void onViewDetachedFromWindow(View v) {
- unregisterSettingsChangeListener();
+ mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
mFragmentService.getFragmentHostManager(mView)
- .removeTagListener(QS.TAG, mFragmentListener);
+ .removeTagListener(QS.TAG, mQsFragmentListener);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mFalsingManager.removeTapListener(mFalsingTapListener);
}
}
- private final class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+ private final class ShadeLayoutChangeListener implements View.OnLayoutChangeListener {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
@@ -5700,7 +5455,7 @@
mHasLayoutedSinceDown = true;
if (mUpdateFlingOnLayout) {
abortAnimations();
- fling(mUpdateFlingVelocity, true /* expands */);
+ fling(mUpdateFlingVelocity);
mUpdateFlingOnLayout = false;
}
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
@@ -5734,14 +5489,11 @@
updateExpandedHeight(getExpandedHeight());
updateHeader();
- // If we are running a size change animation, the animation takes care of the height of
- // the container. However, if we are not animating, we always need to make the QS
- // container
- // the desired height so when closing the QS detail, it stays smaller after the size
- // change
- // animation is finished but the detail view is still being animated away (this
- // animation
- // takes longer than the size change animation).
+ // If we are running a size change animation, the animation takes care of the height
+ // of the container. However, if we are not animating, we always need to make the QS
+ // container the desired height so when closing the QS detail, it stays smaller after
+ // the size change animation is finished but the detail view is still being animated
+ // away (this animation takes longer than the size change animation).
if (mQsSizeChangeAnimator == null && mQs != null) {
mQs.setHeightOverride(mQs.getDesiredHeight());
}
@@ -5767,13 +5519,12 @@
}
}
- private class DebugDrawable extends Drawable {
-
+ private final class DebugDrawable extends Drawable {
private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>();
private final Paint mDebugPaint = new Paint();
@Override
- public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
mDebugTextUsedYPositions.clear();
mDebugPaint.setColor(Color.RED);
@@ -5851,18 +5602,17 @@
}
}
- private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
- public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- // the same types of insets that are handled in NotificationShadeWindowView
- int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
- Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
- mDisplayTopInset = combinedInsets.top;
- mDisplayRightInset = combinedInsets.right;
+ @NonNull
+ private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) {
+ // the same types of insets that are handled in NotificationShadeWindowView
+ int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
+ Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
+ mDisplayTopInset = combinedInsets.top;
+ mDisplayRightInset = combinedInsets.right;
- mNavigationBarBottomHeight = insets.getStableInsetBottom();
- updateMaxHeadsUpTranslation();
- return insets;
- }
+ mNavigationBarBottomHeight = insets.getStableInsetBottom();
+ updateMaxHeadsUpTranslation();
+ return insets;
}
/** Removes any pending runnables that would collapse the panel. */
@@ -5870,9 +5620,6 @@
mView.removeCallbacks(mMaybeHideExpandedRunnable);
}
- @PanelState
- private int mCurrentPanelState = STATE_CLOSED;
-
private void onPanelStateChanged(@PanelState int state) {
updateQSExpansionEnabledAmbient();
@@ -5909,6 +5656,11 @@
}
@VisibleForTesting
+ StateListener getStatusBarStateListener() {
+ return mStatusBarStateListener;
+ }
+
+ @VisibleForTesting
boolean isHintAnimationRunning() {
return mHintAnimationRunning;
}
@@ -5929,6 +5681,7 @@
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
+ mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
if (SPEW_LOGCAT) {
Log.v(TAG,
"NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
@@ -5941,6 +5694,8 @@
// Do not let touches go to shade or QS if the bouncer is visible,
// but still let user swipe down to expand the panel, dismissing the bouncer.
if (mCentralSurfaces.isBouncerShowing()) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "bouncer is showing");
return true;
}
if (mCommandQueue.panelsEnabled()
@@ -5948,15 +5703,21 @@
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "HeadsUpTouchHelper");
return true;
}
if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
&& mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "PulseExpansionHandler");
return true;
}
if (!isFullyCollapsed() && onQsIntercept(event)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
+ debugLog("onQsIntercept true");
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "QsIntercept");
return true;
}
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -5987,6 +5748,9 @@
if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
cancelHeightAnimator();
mTouchSlopExceeded = true;
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
+ + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:"
+ + " false");
return true;
}
mInitialExpandY = y;
@@ -6031,6 +5795,8 @@
&& hAbs > Math.abs(x - mInitialExpandX)) {
cancelHeightAnimator();
startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ mShadeLog.v("NotificationPanelViewController MotionEvent"
+ + " intercepted: startExpandMotion");
return true;
}
}
@@ -6159,7 +5925,6 @@
*
* Flinging is also enabled in order to open or close the shade.
*/
-
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -6175,6 +5940,7 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ mShadeLog.logMotionEvent(event, "onTouch: down action");
startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
mMinExpandHeight = 0.0f;
mPanelClosedOnDown = isFullyCollapsed();
@@ -6263,6 +6029,7 @@
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ mShadeLog.logMotionEvent(event, "onTouch: up/cancel action");
addMovement(event);
endMotionEvent(event, x, y, false /* forceCancel */);
// mHeightAnimator is null, there is no remaining frame, ends instrumenting.
@@ -6279,15 +6046,6 @@
}
}
- /** Listens for config changes. */
- public class OnConfigurationChangedListener implements
- NotificationPanelView.OnConfigurationChangedListener {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- loadDimens();
- }
- }
-
static class SplitShadeTransitionAdapter extends Transition {
private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
@@ -6337,4 +6095,26 @@
return TRANSITION_PROPERTIES;
}
}
+
+ private final class ShadeAccessibilityDelegate extends AccessibilityDelegate {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host,
+ AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action
+ == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
+ || action
+ == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
+ mStatusBarKeyguardViewManager.showBouncer(true);
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 65bd58d..1e63b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -284,7 +284,7 @@
return true;
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// capture all touches if the alt auth bouncer is showing
return true;
}
@@ -322,7 +322,7 @@
handled = !mService.isPulsing();
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// eat the touch
handled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index 084b7dc..bf622c9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -52,6 +52,7 @@
private val centralSurfaces: CentralSurfaces,
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val statusBarStateController: StatusBarStateController,
+ private val shadeLogger: ShadeLogger,
tunerService: TunerService,
dumpManager: DumpManager
) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -77,18 +78,23 @@
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
- if (statusBarStateController.isDozing &&
- singleTapEnabled &&
- !dockManager.isDocked &&
- !falsingManager.isProximityNear &&
- !falsingManager.isFalseTap(LOW_PENALTY)
- ) {
- centralSurfaces.wakeUpIfDozing(
+ val isNotDocked = !dockManager.isDocked
+ shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked)
+ if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) {
+ val proximityIsNotNear = !falsingManager.isProximityNear
+ val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY)
+ shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
+ if (proximityIsNotNear && isNotAFalseTap) {
+ shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
+ centralSurfaces.wakeUpIfDozing(
SystemClock.uptimeMillis(),
notificationShadeWindowView,
- "PULSING_SINGLE_TAP")
+ "PULSING_SINGLE_TAP"
+ )
+ }
return true
}
+ shadeLogger.d("onSingleTapUp event ignored")
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index f389dd9..eaf7fae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -224,6 +224,6 @@
}
private NotificationPanelViewController getNotificationPanelViewController() {
- return getCentralSurfaces().getPanelController();
+ return getCentralSurfaces().getNotificationPanelViewController();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 2b788d8..7615301 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -16,6 +16,10 @@
buffer.log(TAG, LogLevel.VERBOSE, msg)
}
+ fun d(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, LogLevel.DEBUG, msg)
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
@@ -77,4 +81,71 @@
}
)
}
+
+ fun logExpansionChanged(
+ message: String,
+ fraction: Float,
+ expanded: Boolean,
+ tracking: Boolean,
+ dragDownPxAmount: Float,
+ ) {
+ log(LogLevel.VERBOSE, {
+ str1 = message
+ double1 = fraction.toDouble()
+ bool1 = expanded
+ bool2 = tracking
+ long1 = dragDownPxAmount.toLong()
+ }, {
+ "$str1 fraction=$double1,expanded=$bool1," +
+ "tracking=$bool2," + "dragDownPxAmount=$dragDownPxAmount"
+ })
+ }
+
+ fun logQsExpansionChanged(
+ message: String,
+ qsExpanded: Boolean,
+ qsMinExpansionHeight: Int,
+ qsMaxExpansionHeight: Int,
+ stackScrollerOverscrolling: Boolean,
+ dozing: Boolean,
+ qsAnimatorExpand: Boolean,
+ animatingQs: Boolean
+ ) {
+ log(LogLevel.VERBOSE, {
+ str1 = message
+ bool1 = qsExpanded
+ int1 = qsMinExpansionHeight
+ int2 = qsMaxExpansionHeight
+ bool2 = stackScrollerOverscrolling
+ bool3 = dozing
+ bool4 = qsAnimatorExpand
+ // 0 = false, 1 = true
+ long1 = animatingQs.compareTo(false).toLong()
+ }, {
+ "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," +
+ "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," +
+ "animatingQs=$long1"
+ })
+ }
+
+ fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = isDozing
+ bool2 = singleTapEnabled
+ bool3 = isNotDocked
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
+ })
+ }
+
+ fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = proximityIsNotNear
+ bool2 = isNotFalseTap
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index a2e4536..b8302d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -663,7 +663,7 @@
} else {
pulseHeight = height
val overflow = nsslController.setPulseHeight(height)
- notificationPanelController.setOverStrechAmount(overflow)
+ notificationPanelController.setOverStretchAmount(overflow)
val transitionHeight = if (keyguardBypassController.bypassEnabled) height else 0.0f
transitionToShadeAmountCommon(transitionHeight)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 87ef92a..408293c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -496,9 +496,6 @@
return;
}
- final float smallCornerRadius =
- getResources().getDimension(R.dimen.notification_corner_radius_small)
- / getResources().getDimension(R.dimen.notification_corner_radius);
final float viewEnd = viewStart + anv.getActualHeight();
final float cornerAnimationDistance = mCornerAnimationDistance
* mAmbientState.getExpansionFraction();
@@ -509,7 +506,7 @@
final float changeFraction = MathUtils.saturate(
(viewEnd - cornerAnimationTop) / cornerAnimationDistance);
anv.requestBottomRoundness(
- anv.isLastInSection() ? 1f : changeFraction,
+ /* value = */ anv.isLastInSection() ? 1f : changeFraction,
/* animate = */ false,
SourceType.OnScroll);
@@ -517,7 +514,7 @@
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
anv.requestBottomRoundness(
- anv.isLastInSection() ? 1f : smallCornerRadius,
+ /* value = */ anv.isLastInSection() ? 1f : 0f,
/* animate = */ false,
SourceType.OnScroll);
}
@@ -527,16 +524,16 @@
final float changeFraction = MathUtils.saturate(
(viewStart - cornerAnimationTop) / cornerAnimationDistance);
anv.requestTopRoundness(
- anv.isFirstInSection() ? 1f : changeFraction,
- false,
+ /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
+ /* animate = */ false,
SourceType.OnScroll);
} else if (viewStart < cornerAnimationTop) {
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
anv.requestTopRoundness(
- anv.isFirstInSection() ? 1f : smallCornerRadius,
- false,
+ /* value = */ anv.isFirstInSection() ? 1f : 0f,
+ /* animate = */ false,
SourceType.OnScroll);
}
}
@@ -976,6 +973,16 @@
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
+ /**
+ * This method resets the OnScroll roundness of a view to 0f
+ *
+ * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
+ */
+ public static void resetOnScrollRoundness(ExpandableView expandableView) {
+ expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
+ expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+ }
+
public class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8a31ed9..470cbcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -198,6 +198,13 @@
// At this point we just need to initiate the transfer
val summaryUpdate = mPostedEntries[logicalSummary.key]
+ // Because we now know for certain that some child is going to alert for this summary
+ // (as we have found a child to transfer the alert to), mark the group as having
+ // interrupted. This will allow us to know in the future that the "should heads up"
+ // state of this group has already been handled, just not via the summary entry itself.
+ logicalSummary.setInterruption()
+ mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentAlert.key)
+
// If the summary was not attached, then remove the alert from the detached summary.
// Otherwise we can simply ignore its posted update.
if (!isSummaryAttached) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index dfaa291..473c35d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -69,4 +69,13 @@
"updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1"
})
}
+
+ fun logSummaryMarkedInterrupted(summaryKey: String, childKey: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = summaryKey
+ str2 = childKey
+ }, {
+ "marked group summary as interrupted: $str1 for alert transfer to child: $str2"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 26f0ad9..0554fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -44,6 +44,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.NotificationGroupingUtil;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -308,6 +309,11 @@
row.setContentTransformationAmount(0, false /* isLastChild */);
row.setNotificationFaded(mContainingNotificationIsFaded);
+
+ // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
+ // Here we should reset the `OnScroll` roundness only on top-level rows.
+ NotificationShelf.resetOnScrollRoundness(row);
+
// It doesn't make sense to keep old animations around, lets cancel them!
ExpandableViewState viewState = row.getViewState();
if (viewState != null) {
@@ -1377,8 +1383,12 @@
if (child.getVisibility() == View.GONE) {
continue;
}
+ child.requestTopRoundness(
+ /* value = */ 0f,
+ /* animate = */ isShown(),
+ SourceType.DefaultValue);
child.requestBottomRoundness(
- last ? getBottomRoundness() : 0f,
+ /* value = */ last ? getBottomRoundness() : 0f,
/* animate = */ isShown(),
SourceType.DefaultValue);
last = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c4ef28e..2c3330e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -114,6 +114,8 @@
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.LargeScreenUtils;
+import com.google.errorprone.annotations.CompileTimeConstant;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -3693,6 +3695,8 @@
@ShadeViewRefactor(RefactorComponent.INPUT)
void handleEmptySpaceClick(MotionEvent ev) {
+ logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
+ mStatusBarState, mTouchIsClick);
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
final float touchSlop = getTouchSlop(ev);
@@ -3704,12 +3708,34 @@
case MotionEvent.ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
+ debugLog("handleEmptySpaceClick: touch event propagated further");
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
break;
+ default:
+ debugLog("handleEmptySpaceClick: MotionEvent ignored");
}
}
+ private void debugLog(@CompileTimeConstant String s) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.d(s);
+ }
+
+ private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
+ int statusBarState, boolean touchIsClick) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.logEmptySpaceClick(
+ isTouchBelowLastNotification,
+ statusBarState,
+ touchIsClick,
+ MotionEvent.actionToString(ev.getActionMasked()));
+ }
+
@ShadeViewRefactor(RefactorComponent.INPUT)
void initDownStates(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 4c52db7..64dd6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -2,6 +2,7 @@
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
@@ -10,6 +11,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
+import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@@ -56,6 +58,25 @@
"key: $str1 expected: $bool1 actual: $bool2"
})
}
+
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+
+ fun logEmptySpaceClick(
+ isBelowLastNotification: Boolean,
+ statusBarState: Int,
+ touchIsClick: Boolean,
+ motionEventDesc: String
+ ) {
+ buffer.log(TAG, DEBUG, {
+ int1 = statusBarState
+ bool1 = touchIsClick
+ bool2 = isBelowLastNotification
+ str1 = motionEventDesc
+ }, {
+ "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " +
+ "isTouchBelowNotification: $bool2 motionEvent: $str1"
+ })
+ }
}
private const val TAG = "NotificationStackScroll"
\ No newline at end of file
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 75b444f..dc37082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -196,8 +196,6 @@
void collapsePanelOnMainThread();
- void collapsePanelWithDuration(int duration);
-
void togglePanel();
void start();
@@ -305,9 +303,6 @@
void checkBarModes();
- // Called by NavigationBarFragment
- void setQsScrimEnabled(boolean scrimEnabled);
-
void updateBubblesVisibility();
void setInteracting(int barWindow, boolean interacting);
@@ -379,8 +374,6 @@
void showKeyguardImpl();
- boolean isInLaunchTransition();
-
void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
Runnable endRunnable, Runnable cancelRunnable);
@@ -437,8 +430,6 @@
void showPinningEscapeToast();
- KeyguardBottomAreaView getKeyguardBottomAreaView();
-
void setBouncerShowing(boolean bouncerShowing);
void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
@@ -505,12 +496,8 @@
boolean isBouncerShowingOverDream();
- void onBouncerPreHideAnimation();
-
boolean isKeyguardSecure();
- NotificationPanelViewController getPanelController();
-
NotificationGutsManager getGutsManager();
void updateNotificationPanelTouchState();
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 409cad0..be3052e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -483,7 +483,7 @@
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
- private final com.android.systemui.shade.ShadeController mShadeController;
+ private final ShadeController mShadeController;
private final InitController mInitController;
private final PluginDependencyProvider mPluginDependencyProvider;
@@ -496,9 +496,9 @@
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
- // expanded notifications
- // the sliding/resizing panel within the notification window
- protected NotificationPanelViewController mNotificationPanelViewController;
+ /** Controller for the Shade. */
+ @VisibleForTesting
+ NotificationPanelViewController mNotificationPanelViewController;
// settings
private QSPanelController mQSPanelController;
@@ -1169,7 +1169,6 @@
initializer.initializeStatusBar(mCentralSurfacesComponent);
mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
- mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
createNavigationBar(result);
@@ -1178,9 +1177,6 @@
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
-
mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
R.id.ambient_indication_container);
@@ -2021,8 +2017,7 @@
}
void makeExpandedInvisible() {
- if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
- + " mExpandedVisible=" + mExpandedVisible);
+ if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
if (!mExpandedVisible || mNotificationShadeWindowView == null) {
return;
@@ -2198,12 +2193,6 @@
mNoAnimationOnNextBarModeChange = false;
}
- // Called by NavigationBarFragment
- @Override
- public void setQsScrimEnabled(boolean scrimEnabled) {
- mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
- }
-
/** Temporarily hides Bubbles if the status bar is hidden. */
@Override
public void updateBubblesVisibility() {
@@ -2579,14 +2568,11 @@
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */, true /* delayed*/);
} else {
-
// Do it after DismissAction has been processed to conserve the needed
// ordering.
mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
}
- } else if (CentralSurfacesImpl.this.isInLaunchTransition()
- && mNotificationPanelViewController.isLaunchTransitionFinished()) {
-
+ } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
// We are not dismissing the shade, but the launch transition is already
// finished,
// so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -3008,11 +2994,6 @@
mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
}
- @Override
- public boolean isInLaunchTransition() {
- return mNotificationPanelViewController.isLaunchTransitionFinished();
- }
-
/**
* Fades the content of the keyguard away after the launch transition is done.
*
@@ -3392,12 +3373,6 @@
}
}
- /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */
- @Override
- public void collapsePanelWithDuration(int duration) {
- mNotificationPanelViewController.collapseWithDuration(duration);
- }
-
/**
* Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
* from the power button).
@@ -3487,15 +3462,6 @@
mNavigationBarController.showPinningEscapeToast(mDisplayId);
}
- /**
- * TODO: Remove this method. Views should not be passed forward. Will cause theme issues.
- * @return bottom area view
- */
- @Override
- public KeyguardBottomAreaView getKeyguardBottomAreaView() {
- return mNotificationPanelViewController.getKeyguardBottomAreaView();
- }
-
protected ViewRootImpl getViewRootImpl() {
NotificationShadeWindowView nswv = getNotificationShadeWindowView();
if (nswv != null) return nswv.getViewRootImpl();
@@ -4198,23 +4164,11 @@
return mBouncerShowingOverDream;
}
- /**
- * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
- */
- @Override
- public void onBouncerPreHideAnimation() {
- mNotificationPanelViewController.onBouncerPreHideAnimation();
-
- }
-
@Override
public boolean isKeyguardSecure() {
return mStatusBarKeyguardViewManager.isSecure();
}
- @Override
- public NotificationPanelViewController getPanelController() {
- return mNotificationPanelViewController;
- }
+
// End Extra BaseStatusBarMethods.
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 9bb4132..b2a9509 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -64,6 +64,11 @@
private static final String TAG = "KeyguardBouncer";
static final long BOUNCER_FACE_DELAY = 1200;
public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
+ /**
+ * Values for the bouncer expansion represented as the panel expansion.
+ * Panel expansion 1f = panel fully showing = bouncer fully hidden
+ * Panel expansion 0f = panel fully hiding = bouncer fully showing
+ */
public static final float EXPANSION_HIDDEN = 1f;
public static final float EXPANSION_VISIBLE = 0f;
@@ -143,6 +148,14 @@
}
/**
+ * Get the KeyguardBouncer expansion
+ * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
+ */
+ public float getExpansion() {
+ return mExpansion;
+ }
+
+ /**
* Enable/disable only the back button
*/
public void setBackButtonEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ccb5d88..a00e756 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -86,8 +86,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -166,13 +168,9 @@
@Override
public void onExpansionChanged(float expansion) {
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
- }
if (mBouncerAnimating) {
mCentralSurfaces.setBouncerHiddenFraction(expansion);
}
- updateStates();
}
@Override
@@ -184,9 +182,6 @@
if (!isVisible) {
mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
}
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.onBouncerVisibilityChanged();
- }
/* Register predictive back callback when keyguard becomes visible, and unregister
when it's hidden. */
@@ -252,6 +247,7 @@
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+ final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernBouncerEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -465,7 +461,7 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
@@ -475,17 +471,17 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
} else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
- && !mCentralSurfaces.isInLaunchTransition()
+ && !mNotificationPanelViewController.isLaunchTransitionFinished()
&& !isUnlockCollapsing()) {
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
} else {
- mBouncerInteractor.setExpansion(fraction);
+ mBouncerInteractor.setPanelExpansion(fraction);
}
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
@@ -504,7 +500,7 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -849,7 +845,7 @@
if (isShowing && isOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
- if (mCentralSurfaces.isInLaunchTransition()) {
+ if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
final Runnable endRunnable = new Runnable() {
@Override
public void run() {
@@ -903,7 +899,7 @@
} else {
mBouncerInteractor.startDisappearAnimation(finishRunnable);
}
- mCentralSurfaces.onBouncerPreHideAnimation();
+ mNotificationPanelViewController.startBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
@@ -935,7 +931,7 @@
long uptimeMillis = SystemClock.uptimeMillis();
long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
- if (mCentralSurfaces.isInLaunchTransition()
+ if (mNotificationPanelViewController.isLaunchTransitionFinished()
|| mKeyguardStateController.isFlingingToDismissKeyguard()) {
final boolean wasFlingingToDismissKeyguard =
mKeyguardStateController.isFlingingToDismissKeyguard();
@@ -1313,7 +1309,7 @@
@Override
public boolean shouldDisableWindowAnimationsForUnlock() {
- return mCentralSurfaces.isInLaunchTransition();
+ return mNotificationPanelViewController.isLaunchTransitionFinished();
}
@Override
@@ -1356,7 +1352,7 @@
mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
+ if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
resetAlternateAuth(false);
executeAfterKeyguardGoneAction();
}
@@ -1442,6 +1438,10 @@
pw.println(" mPendingWakeupAction: " + mPendingWakeupAction);
pw.println(" isBouncerShowing(): " + isBouncerShowing());
pw.println(" bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+ pw.println(" Registered KeyguardViewManagerCallbacks:");
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ pw.println(" " + callback);
+ }
if (mBouncer != null) {
mBouncer.dump(pw);
@@ -1466,6 +1466,20 @@
}
/**
+ * Add a callback to listen for changes
+ */
+ public void addCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ /**
+ * Removes callback to stop receiving updates
+ */
+ public void removeCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ /**
* Whether qs is currently expanded.
*/
public float getQsExpansion() {
@@ -1477,8 +1491,8 @@
*/
public void setQsExpansion(float qsExpansion) {
mQsExpansion = qsExpansion;
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setQsExpansion(qsExpansion);
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ callback.onQSExpansionChanged(mQsExpansion);
}
}
@@ -1492,21 +1506,13 @@
&& mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
}
- public boolean isShowingAlternateAuthOrAnimating() {
- return mAlternateAuthInterceptor != null
- && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
- || mAlternateAuthInterceptor.isAnimating());
- }
-
/**
- * Forward touches to any alternate authentication affordances.
+ * Forward touches to callbacks.
*/
- public boolean onTouch(MotionEvent event) {
- if (mAlternateAuthInterceptor == null) {
- return false;
+ public void onTouch(MotionEvent event) {
+ for (KeyguardViewManagerCallback callback: mCallbacks) {
+ callback.onTouch(event);
}
-
- return mAlternateAuthInterceptor.onTouch(event);
}
/** Update keyguard position based on a tapped X coordinate. */
@@ -1640,38 +1646,6 @@
boolean isShowingAlternateAuthBouncer();
/**
- * print information for the alternate auth interceptor registered
- */
- void dump(PrintWriter pw);
-
- /**
- * @return true if the new auth method bouncer is currently animating in or out.
- */
- boolean isAnimating();
-
- /**
- * How much QS is fully expanded where 0f is not showing and 1f is fully expanded.
- */
- void setQsExpansion(float qsExpansion);
-
- /**
- * Forward potential touches to authentication interceptor
- * @return true if event was handled
- */
- boolean onTouch(MotionEvent event);
-
- /**
- * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully
- * hidden
- */
- void setBouncerExpansionChanged(float expansion);
-
- /**
- * called when the bouncer view visibility has changed.
- */
- void onBouncerVisibilityChanged();
-
- /**
* Use when an app occluding the keyguard would like to give the user ability to
* unlock the device using udfps.
*
@@ -1680,5 +1654,25 @@
*/
void requestUdfps(boolean requestUdfps, int color);
+ /**
+ * print information for the alternate auth interceptor registered
+ */
+ void dump(PrintWriter pw);
+ }
+
+ /**
+ * Callback for KeyguardViewManager state changes.
+ */
+ public interface KeyguardViewManagerCallback {
+ /**
+ * Set the amount qs is expanded. For example, swipe down from the top of the
+ * lock screen to start the full QS expansion.
+ */
+ default void onQSExpansionChanged(float qsExpansion) { }
+
+ /**
+ * Forward touch events to callbacks
+ */
+ default void onTouch(MotionEvent event) { }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index ee948c0..b1642d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -31,7 +31,7 @@
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
- centralSurfaces.collapsePanelWithDuration(
+ centralSurfaces.notificationPanelViewController.collapseWithDuration(
ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
new file mode 100644
index 0000000..e618905
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.model
+
+import android.net.NetworkCapabilities
+
+/** Provides information about a mobile network connection */
+data class MobileConnectivityModel(
+ /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
+ val isConnected: Boolean = false,
+ /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
+ val isValidated: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 06e8f46..581842b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings.Global
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
@@ -34,6 +38,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.util.settings.GlobalSettings
import java.lang.IllegalStateException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,9 +47,12 @@
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -65,14 +73,23 @@
*/
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
- val dataEnabled: Flow<Boolean>
+ val dataEnabled: StateFlow<Boolean>
+ /**
+ * True if this connection represents the default subscription per
+ * [SubscriptionManager.getDefaultDataSubscriptionId]
+ */
+ val isDefaultDataSubscription: StateFlow<Boolean>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
+ private val context: Context,
private val subId: Int,
private val telephonyManager: TelephonyManager,
+ private val globalSettings: GlobalSettings,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
scope: CoroutineScope,
@@ -86,6 +103,8 @@
}
}
+ private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
var state = MobileSubscriptionModel()
conflatedCallbackFlow {
@@ -165,33 +184,75 @@
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .onEach { telephonyCallbackEvent.tryEmit(Unit) }
.logOutputChange(logger, "MobileSubscriptionModel")
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
+ /** Produces whenever the mobile data setting changes for this subId */
+ private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
+ /* notifyForDescendants */ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
/**
* There are a few cases where we will need to poll [TelephonyManager] so we can update some
* internal state where callbacks aren't provided. Any of those events should be merged into
* this flow, which can be used to trigger the polling.
*/
- private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
+ private val telephonyPollingEvent: Flow<Unit> =
+ merge(
+ telephonyCallbackEvent,
+ localMobileDataSettingChangedEvent,
+ globalMobileDataSettingChangedEvent,
+ )
- override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
+ override val dataEnabled: StateFlow<Boolean> =
+ telephonyPollingEvent
+ .mapLatest { dataConnectionAllowed() }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
+ override val isDefaultDataSubscription: StateFlow<Boolean> =
+ defaultDataSubId
+ .mapLatest { it == subId }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+
class Factory
@Inject
constructor(
+ private val context: Context,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
+ private val globalSettings: GlobalSettings,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) {
- fun build(subId: Int): MobileConnectionRepository {
+ fun build(
+ subId: Int,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
+ ): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
+ context,
subId,
telephonyManager.createForSubscriptionId(subId),
+ globalSettings,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
bgDispatcher,
logger,
scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 0e2428a..c3c1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -16,15 +16,27 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.annotation.SuppressLint
import android.content.Context
import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
+import android.provider.Settings.Global.MOBILE_DATA
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,7 +44,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -40,10 +54,12 @@
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -57,13 +73,22 @@
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
/** Observable for the subscriptionId of the current mobile data connection */
- val activeMobileDataSubscriptionId: Flow<Int>
+ val activeMobileDataSubscriptionId: StateFlow<Int>
/** Observable for [MobileMappings.Config] tracking the defaults */
val defaultDataSubRatConfig: StateFlow<Config>
+ /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
+ val defaultDataSubId: StateFlow<Int>
+
+ /** The current connectivity status for the default mobile network connection */
+ val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
+
/** Get or create a repository for the line of service for the given subscription ID */
fun getRepoForSubId(subId: Int): MobileConnectionRepository
+
+ /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
+ val globalMobileDataSettingChangedEvent: Flow<Unit>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -72,10 +97,12 @@
class MobileConnectionsRepositoryImpl
@Inject
constructor(
+ private val connectivityManager: ConnectivityManager,
private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
broadcastDispatcher: BroadcastDispatcher,
+ private val globalSettings: GlobalSettings,
private val context: Context,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@@ -121,17 +148,26 @@
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+
+ private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
+ override val defaultDataSubId: StateFlow<Int> =
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ ) { intent, _ ->
+ intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ }
+ .distinctUntilChanged()
+ .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn(
scope,
- started = SharingStarted.WhileSubscribed(),
- SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ SharingStarted.WhileSubscribed(),
+ SubscriptionManager.getDefaultDataSubscriptionId()
)
- private val defaultDataSubChangedEvent =
- broadcastDispatcher.broadcastFlow(
- IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- )
-
private val carrierConfigChangedEvent =
broadcastDispatcher.broadcastFlow(
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
@@ -148,9 +184,8 @@
* This flow will produce whenever the default data subscription or the carrier config changes.
*/
override val defaultDataSubRatConfig: StateFlow<Config> =
- combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
- Config.readConfig(context)
- }
+ merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+ .mapLatest { Config.readConfig(context) }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -168,6 +203,57 @@
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
}
+ /**
+ * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
+ * connection repositories also observe the URI for [MOBILE_DATA] + subId.
+ */
+ override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor(MOBILE_DATA),
+ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
+ @SuppressLint("MissingPermission")
+ override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+ conflatedCallbackFlow {
+ val callback =
+ object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onLost(network: Network) {
+ // Send a disconnected model when lost. Maybe should create a sealed
+ // type or null here?
+ trySend(MobileConnectivityModel())
+ }
+
+ override fun onCapabilitiesChanged(
+ network: Network,
+ caps: NetworkCapabilities
+ ) {
+ trySend(
+ MobileConnectivityModel(
+ isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
+ isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
+ )
+ )
+ }
+ }
+
+ connectivityManager.registerDefaultNetworkCallback(callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
+
private fun isValidSubId(subId: Int): Boolean {
subscriptionsFlow.value.forEach {
if (it.subscriptionId == subId) {
@@ -181,7 +267,11 @@
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
- return mobileConnectionRepositoryFactory.build(subId)
+ return mobileConnectionRepositoryFactory.build(
+ subId,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
+ )
}
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
index 77de849..91886bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -26,7 +26,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapLatest
@@ -40,7 +39,7 @@
*/
interface UserSetupRepository {
/** Observable tracking [DeviceProvisionedController.isUserSetup] */
- val isUserSetupFlow: Flow<Boolean>
+ val isUserSetupFlow: StateFlow<Boolean>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index f99d278c..0da84f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -18,81 +18,109 @@
import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
+ /** Only true if mobile is the default transport but is not validated, otherwise false */
+ val isDefaultConnectionFailed: StateFlow<Boolean>
+
+ /** True when telephony tells us that the data state is CONNECTED */
+ val isDataConnected: StateFlow<Boolean>
+
+ // TODO(b/256839546): clarify naming of default vs active
+ /** True if we want to consider the data connection enabled */
+ val isDefaultDataEnabled: StateFlow<Boolean>
+
/** Observable for the data enabled state of this connection */
- val isDataEnabled: Flow<Boolean>
+ val isDataEnabled: StateFlow<Boolean>
/** Observable for RAT type (network type) indicator */
- val networkTypeIconGroup: Flow<MobileIconGroup>
+ val networkTypeIconGroup: StateFlow<MobileIconGroup>
/** True if this line of service is emergency-only */
- val isEmergencyOnly: Flow<Boolean>
+ val isEmergencyOnly: StateFlow<Boolean>
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
- val level: Flow<Int>
+ val level: StateFlow<Int>
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
- val numberOfLevels: Flow<Int>
-
- /** True when we want to draw an icon that makes room for the exclamation mark */
- val cutOut: Flow<Boolean>
+ val numberOfLevels: StateFlow<Int>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconInteractorImpl(
- defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
- defaultMobileIconGroup: Flow<MobileIconGroup>,
+ @Application scope: CoroutineScope,
+ defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+ defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: StateFlow<MobileIconGroup>,
+ override val isDefaultConnectionFailed: StateFlow<Boolean>,
mobileMappingsProxy: MobileMappingsProxy,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
- override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled
+ override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+
+ override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
- override val networkTypeIconGroup: Flow<MobileIconGroup> =
+ override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
- mobileStatusInfo,
- defaultMobileIconMapping,
- defaultMobileIconGroup,
- ) { info, mapping, defaultGroup ->
- val lookupKey =
- when (val resolved = info.resolvedNetworkType) {
- is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
- is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
- }
- mapping[lookupKey] ?: defaultGroup
- }
-
- override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
-
- override val level: Flow<Int> =
- mobileStatusInfo.map { mobileModel ->
- // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
- if (mobileModel.isGsm) {
- mobileModel.primaryLevel
- } else {
- mobileModel.cdmaLevel
+ mobileStatusInfo,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ ) { info, mapping, defaultGroup ->
+ val lookupKey =
+ when (val resolved = info.resolvedNetworkType) {
+ is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
+ is OverrideNetworkType ->
+ mobileMappingsProxy.toIconKeyOverride(resolved.type)
+ }
+ mapping[lookupKey] ?: defaultGroup
}
- }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+
+ override val isEmergencyOnly: StateFlow<Boolean> =
+ mobileStatusInfo
+ .mapLatest { it.isEmergencyOnly }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val level: StateFlow<Int> =
+ mobileStatusInfo
+ .mapLatest { mobileModel ->
+ // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+ if (mobileModel.isGsm) {
+ mobileModel.primaryLevel
+ } else {
+ mobileModel.cdmaLevel
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
/**
* This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
* once it's wired up inside of [CarrierConfigTracker]
*/
- override val numberOfLevels: Flow<Int> = flowOf(4)
+ override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
- /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
- // TODO: find a better name for this?
- override val cutOut: Flow<Boolean> = flowOf(false)
+ override val isDataConnected: StateFlow<Boolean> =
+ mobileStatusInfo
+ .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 614d583..a4175c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -19,6 +19,7 @@
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
@@ -35,7 +36,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/**
@@ -51,12 +54,16 @@
interface MobileIconsInteractor {
/** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ /** True if the active mobile data subscription has data enabled */
+ val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
- val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+ val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
- val defaultMobileIconGroup: Flow<MobileIconGroup>
+ val defaultMobileIconGroup: StateFlow<MobileIconGroup>
+ /** True only if the default network is mobile, and validation also failed */
+ val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */
- val isUserSetup: Flow<Boolean>
+ val isUserSetup: StateFlow<Boolean>
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
* subId. Will throw if the ID is invalid
@@ -79,6 +86,22 @@
private val activeMobileDataSubscriptionId =
mobileConnectionsRepo.activeMobileDataSubscriptionId
+ private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
+ activeMobileDataSubscriptionId
+ .mapLatest { activeId ->
+ if (activeId == INVALID_SUBSCRIPTION_ID) {
+ null
+ } else {
+ mobileConnectionsRepo.getRepoForSubId(activeId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
+ activeMobileDataConnectionRepo
+ .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
mobileConnectionsRepo.subscriptionsFlow
@@ -132,22 +155,40 @@
*/
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
mobileConnectionsRepo.defaultDataSubRatConfig
- .map { mobileMappingsProxy.mapIconSets(it) }
+ .mapLatest { mobileMappingsProxy.mapIconSets(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileConnectionsRepo.defaultDataSubRatConfig
- .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
- override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+ /**
+ * We want to show an error state when cellular has actually failed to validate, but not if some
+ * other transport type is active, because then we expect there not to be validation.
+ */
+ override val isDefaultConnectionFailed: StateFlow<Boolean> =
+ mobileConnectionsRepo.defaultMobileNetworkConnectivity
+ .mapLatest { connectivityModel ->
+ if (!connectivityModel.isConnected) {
+ false
+ } else {
+ !connectivityModel.isValidated
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
/** Vends out new [MobileIconInteractor] for a particular subId */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl(
+ scope,
+ activeDataConnectionHasDataEnabled,
defaultMobileIconMapping,
defaultMobileIconGroup,
+ isDefaultConnectionFailed,
mobileMappingsProxy,
mobileConnectionsRepo.getRepoForSubId(subId),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 8131739..7869021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -24,10 +24,12 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -39,29 +41,38 @@
*
* TODO: figure out where carrier merged and VCN models go (probably here?)
*/
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel
constructor(
val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger,
) {
+ /** Whether or not to show the error state of [SignalDrawable] */
+ private val showExclamationMark: Flow<Boolean> =
+ iconInteractor.isDefaultDataEnabled.mapLatest { !it }
+
/** An int consumable by [SignalDrawable] for display */
- var iconId: Flow<Int> =
- combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+ val iconId: Flow<Int> =
+ combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
level,
numberOfLevels,
- cutOut ->
- SignalDrawable.getState(level, numberOfLevels, cutOut)
+ showExclamationMark ->
+ SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
}
.distinctUntilChanged()
.logOutputChange(logger, "iconId($subscriptionId)")
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- var networkTypeIcon: Flow<Icon?> =
- combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
- networkTypeIconGroup,
- isDataEnabled ->
- if (!isDataEnabled) {
+ val networkTypeIcon: Flow<Icon?> =
+ combine(
+ iconInteractor.networkTypeIconGroup,
+ iconInteractor.isDataConnected,
+ iconInteractor.isDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
+ if (!dataConnected || !dataEnabled || failedConnection) {
null
} else {
val desc =
@@ -72,5 +83,5 @@
}
}
- var tint: Flow<Int> = flowOf(Color.CYAN)
+ val tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index bc2ae64..e326611 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -67,7 +67,7 @@
internal const val QS_DEFAULT_POSITION = 7
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
- internal const val PREFS_CONTROLS_FILE = "controls_prefs"
+ const val PREFS_CONTROLS_FILE = "controls_prefs"
internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
private const val SEEDING_MAX = 2
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c0..131cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@
keyguardOccluded = false,
occludingAppRequestingFp = false,
primaryUser = false,
+ shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index f9bec65..52f8ef8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -55,6 +55,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.SessionTracker;
@@ -143,6 +144,8 @@
private SidefpsController mSidefpsController;
@Mock
private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
+ @Mock
+ private FalsingA11yDelegate mFalsingA11yDelegate;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@@ -186,7 +189,8 @@
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings,
- mSessionTracker, Optional.of(mSidefpsController)).create(mSecurityCallback);
+ mSessionTracker, Optional.of(mSidefpsController), mFalsingA11yDelegate).create(
+ mSecurityCallback);
}
@Test
@@ -225,7 +229,8 @@
mKeyguardSecurityContainerController.updateResources();
verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
// Update rotation. Should trigger update
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
@@ -233,7 +238,8 @@
mKeyguardSecurityContainerController.updateResources();
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
private void touchDown() {
@@ -269,7 +275,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
@Test
@@ -282,7 +289,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
@Test
@@ -293,7 +301,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
eq(mUserSwitcherController),
- any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
}
@Test
@@ -307,7 +316,8 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
any(UserSwitcherController.class),
- captor.capture());
+ captor.capture(),
+ eq(mFalsingA11yDelegate));
captor.getValue().showUnlockToContinueMessage();
verify(mKeyguardPasswordViewControllerMock).showMessage(
getContext().getString(R.string.keyguard_unlock_to_continue), null);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 82d3ca7..1bd14e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -31,6 +31,7 @@
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
+import static com.android.keyguard.KeyguardSecurityContainer.MODE_USER_SWITCHER;
import static com.google.common.truth.Truth.assertThat;
@@ -54,6 +55,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
@@ -87,6 +89,8 @@
private FalsingManager mFalsingManager;
@Mock
private UserSwitcherController mUserSwitcherController;
+ @Mock
+ private FalsingA11yDelegate mFalsingA11yDelegate;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
@@ -111,15 +115,14 @@
when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
}
+
@Test
public void testOnApplyWindowInsets() {
int paddingBottom = getContext().getResources()
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
int imeInsetAmount = paddingBottom + 1;
int systemBarInsetAmount = 0;
-
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(MODE_DEFAULT);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -140,8 +143,7 @@
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
int systemBarInsetAmount = paddingBottom + 1;
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(MODE_DEFAULT);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -157,11 +159,8 @@
@Test
public void testDefaultViewMode() {
- mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {
- });
- mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(MODE_ONE_HANDED);
+ initMode(MODE_DEFAULT);
ConstraintSet.Constraint viewFlipperConstraint =
getViewConstraint(mSecurityViewFlipper.getId());
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
@@ -377,8 +376,7 @@
private void setupUserSwitcher() {
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
- mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {});
+ initMode(MODE_USER_SWITCHER);
}
private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -396,8 +394,7 @@
private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
- mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
- mUserSwitcherController, () -> {});
+ initMode(mode);
}
/** Get the ConstraintLayout constraint of the view. */
@@ -406,4 +403,10 @@
constraintSet.clone(mKeyguardSecurityContainer);
return constraintSet.getConstraint(viewId);
}
+
+ private void initMode(int mode) {
+ mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+ mUserSwitcherController, () -> {
+ }, mFalsingA11yDelegate);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5104f84..a8284d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -20,6 +20,7 @@
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -38,6 +39,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -53,6 +55,7 @@
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -60,18 +63,21 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -110,6 +116,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -181,6 +188,8 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private SecureSettings mSecureSettings;
+ @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -214,6 +223,7 @@
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -223,6 +233,9 @@
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+ @Mock
+ private Uri mURI;
+
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+
+ when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ ExtendedMockito.spyOn(contentResolver);
+ doNothing().when(contentResolver)
+ .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+ anyInt());
+
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
@@ -1136,6 +1158,64 @@
}
@Test
+ public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+ throws RemoteException {
+ // SFPS supported and enrolled
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+ // WHEN require screen on to auth is disabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+ // Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+
+ statusBarShadeIsLocked();
+ mTestableLooper.processAllMessages();
+
+ // THEN we should listen for sfps when screen off, because require screen on is disabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+ // WHEN require screen on to auth is enabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+ // Device now awake & keyguard is now interactive
+ deviceNotGoingToSleep();
+ deviceIsInteractive();
+ keyguardIsVisible();
+
+ // THEN we should listen for sfps when screen on, and require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
+
+
+ private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType) {
+ return new FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ new ArrayList<ComponentInfoInternal>(),
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
+ @Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1804,7 +1884,7 @@
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mDumpManager,
+ mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e8c760c..d1107c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,6 +35,8 @@
import android.view.WindowManager
import android.widget.ScrollView
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
@@ -159,6 +161,35 @@
}
@Test
+ fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
+ val container = initializeFingerprintContainer(
+ authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ waitForIdleSync()
+
+ val requestID = authContainer?.requestId ?: 0L
+
+ // Simulate keyboard was shown on the credential view
+ val windowInsetsController = container.windowInsetsController
+ spyOn(windowInsetsController)
+ spyOn(container.rootWindowInsets)
+ doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime())
+
+ container.onWindowFocusChanged(false)
+ waitForIdleSync()
+
+ // Expect hiding IME request will be invoked when dismissing the view
+ verify(windowInsetsController)?.hide(WindowInsets.Type.ime())
+
+ verify(callback).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(requestID)
+ )
+ assertThat(container.parent).isNull()
+ }
+
+ @Test
fun testActionAuthenticated_sendsDismissedAuthenticated() {
val container = initializeFingerprintContainer()
container.mBiometricCallback.onAction(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt
new file mode 100644
index 0000000..fbda08f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.PromptInfo
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.Handler
+import android.os.IBinder
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthContainerView.BiometricCallback
+import com.android.systemui.biometrics.AuthCredentialView.ErrorTimer
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class AuthCredentialViewTest : SysuiTestCase() {
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock lateinit var callback: AuthDialogCallback
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock lateinit var windowToken: IBinder
+ @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+
+ private var authContainer: TestAuthContainerView? = null
+ private var authCredentialView: AuthCredentialPatternView? = null
+ private var lockPatternView: LockPatternView? = null
+ private var biometricCallback: BiometricCallback? = null
+ private var errorTimer: ErrorTimer? = null
+
+ @After
+ fun tearDown() {
+ if (authContainer?.isAttachedToWindow == true) {
+ ViewUtils.detachView(authContainer)
+ }
+ }
+
+ @Test
+ fun testAuthCredentialPatternView_onErrorTimeoutFinish_setPatternEnabled() {
+ `when`(lockPatternUtils.getCredentialTypeForUser(anyInt()))
+ .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PATTERN)
+ `when`(lockPatternUtils.getKeyguardStoredPasswordQuality(anyInt()))
+ .thenReturn(PASSWORD_QUALITY_SOMETHING)
+ val errorResponse: VerifyCredentialResponse = VerifyCredentialResponse.fromError()
+
+ assertThat(initializeFingerprintContainer()).isNotNull()
+ authContainer?.animateToCredentialUI()
+ waitForIdleSync()
+
+ authCredentialView = spy(authContainer?.mCredentialView as AuthCredentialPatternView)
+ authCredentialView?.onCredentialVerified(errorResponse, 5000)
+ errorTimer = authCredentialView?.mErrorTimer
+ errorTimer?.onFinish()
+ waitForIdleSync()
+
+ verify(authCredentialView)?.onErrorTimeoutFinish()
+
+ lockPatternView = authCredentialView?.mLockPatternView
+ assertThat(lockPatternView?.isEnabled).isTrue()
+ }
+
+ private fun initializeFingerprintContainer(
+ authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+ addToView: Boolean = true
+ ) =
+ initializeContainer(
+ TestAuthContainerView(
+ authenticators = authenticators,
+ fingerprintProps = fingerprintSensorPropertiesInternal()
+ ),
+ addToView
+ )
+
+ private fun initializeContainer(
+ view: TestAuthContainerView,
+ addToView: Boolean
+ ): TestAuthContainerView {
+ authContainer = view
+ if (addToView) {
+ authContainer!!.addToView()
+ biometricCallback = authContainer?.mBiometricCallback
+ }
+ return authContainer!!
+ }
+
+ private inner class TestAuthContainerView(
+ authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+ fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(),
+ faceProps: List<FaceSensorPropertiesInternal> = listOf()
+ ) :
+ AuthContainerView(
+ Config().apply {
+ mContext = context
+ mCallback = callback
+ mSensorIds =
+ (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId })
+ .toIntArray()
+ mSkipAnimation = true
+ mPromptInfo = PromptInfo().apply { this.authenticators = authenticators }
+ },
+ fingerprintProps,
+ faceProps,
+ wakefulnessLifecycle,
+ userManager,
+ lockPatternUtils,
+ interactionJankMonitor,
+ Handler(TestableLooper.get(this).looper),
+ FakeExecutor(FakeSystemClock())
+ ) {
+ override fun postOnAnimation(runnable: Runnable) {
+ runnable.run()
+ }
+ }
+
+ override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
+
+ private fun AuthContainerView.addToView() {
+ ViewUtils.attachView(this)
+ waitForIdleSync()
+ assertThat(isAttachedToWindow).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index c85334d..90948ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -42,6 +42,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -103,6 +105,8 @@
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var bouncerInteractor: BouncerInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -136,7 +140,8 @@
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
+ controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
+ bouncerInteractor, isDebuggable
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 28e13b8..be39c0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -69,7 +69,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -171,6 +173,8 @@
private FakeExecutor mFgExecutor;
@Mock
private UdfpsDisplayMode mUdfpsDisplayMode;
+ @Mock
+ private FeatureFlags mFeatureFlags;
// Stuff for configuring mocks
@Mock
@@ -191,6 +195,8 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+ @Mock
+ private BouncerInteractor mBouncerInteractor;
// Capture listeners so that they can be used to send events
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
@@ -252,6 +258,7 @@
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
+ mFeatureFlags,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
@@ -270,7 +277,8 @@
mLatencyTracker,
mActivityLaunchAnimator,
Optional.of(mAlternateTouchProvider),
- mBiometricsExecutor);
+ mBiometricsExecutor,
+ mBouncerInteractor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
new file mode 100644
index 0000000..e5c7a42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
+ // Dependencies
+ protected @Mock UdfpsKeyguardView mView;
+ protected @Mock Context mResourceContext;
+ protected @Mock StatusBarStateController mStatusBarStateController;
+ protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+ protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ protected @Mock DumpManager mDumpManager;
+ protected @Mock DelayableExecutor mExecutor;
+ protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ protected @Mock KeyguardStateController mKeyguardStateController;
+ protected @Mock KeyguardViewMediator mKeyguardViewMediator;
+ protected @Mock ConfigurationController mConfigurationController;
+ protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ protected @Mock SystemUIDialogManager mDialogManager;
+ protected @Mock UdfpsController mUdfpsController;
+ protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ protected @Mock KeyguardBouncer mBouncer;
+ protected @Mock BouncerInteractor mBouncerInteractor;
+
+ protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ protected FakeSystemClock mSystemClock = new FakeSystemClock();
+
+ protected UdfpsKeyguardViewController mController;
+
+ // Capture listeners so that they can be used to send events
+ private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+ protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+ private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+ protected List<ShadeExpansionListener> mExpansionListeners;
+
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
+ mAltAuthInterceptorCaptor;
+ protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+
+ private @Captor ArgumentCaptor<KeyguardStateController.Callback>
+ mKeyguardStateControllerCallbackCaptor;
+ protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mView.getContext()).thenReturn(mResourceContext);
+ when(mResourceContext.getString(anyInt())).thenReturn("test string");
+ when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ mController = createUdfpsKeyguardViewController();
+ }
+
+ protected void sendStatusBarStateChanged(int statusBarState) {
+ mStatusBarStateListener.onStateChanged(statusBarState);
+ }
+
+ protected void captureStatusBarStateListeners() {
+ verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+ mStatusBarStateListener = mStateListenerCaptor.getValue();
+ }
+
+ protected void captureStatusBarExpansionListeners() {
+ verify(mShadeExpansionStateManager, times(2))
+ .addExpansionListener(mExpansionListenerCaptor.capture());
+ // first (index=0) is from super class, UdfpsAnimationViewController.
+ // second (index=1) is from UdfpsKeyguardViewController
+ mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+ }
+
+ protected void updateStatusBarExpansion(float fraction, boolean expanded) {
+ ShadeExpansionChangeEvent event =
+ new ShadeExpansionChangeEvent(
+ fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
+ for (ShadeExpansionListener listener : mExpansionListeners) {
+ listener.onPanelExpansionChanged(event);
+ }
+ }
+
+ protected void captureAltAuthInterceptor() {
+ verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
+ mAltAuthInterceptorCaptor.capture());
+ mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+ }
+
+ protected void captureKeyguardStateControllerCallback() {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateControllerCallbackCaptor.capture());
+ mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ }
+
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(false);
+ }
+
+ protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
+ boolean useModernBouncer) {
+ mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+ when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+ useModernBouncer ? null : mBouncer);
+ return new UdfpsKeyguardViewController(
+ mView,
+ mStatusBarStateController,
+ mShadeExpansionStateManager,
+ mStatusBarKeyguardViewManager,
+ mKeyguardUpdateMonitor,
+ mDumpManager,
+ mLockscreenShadeTransitionController,
+ mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
+ mDialogManager,
+ mUdfpsController,
+ mActivityLaunchAnimator,
+ mFeatureFlags,
+ mBouncerInteractor);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index c0f9c82..55b6194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -25,126 +25,53 @@
import static org.mockito.Mockito.eq;
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;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
-public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
- // Dependencies
- @Mock
- private UdfpsKeyguardView mView;
- @Mock
- private Context mResourceContext;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private ShadeExpansionStateManager mShadeExpansionStateManager;
- @Mock
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock
- private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- @Mock
- private DumpManager mDumpManager;
- @Mock
- private DelayableExecutor mExecutor;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- @Mock
- private SystemUIDialogManager mDialogManager;
- @Mock
- private UdfpsController mUdfpsController;
- @Mock
- private ActivityLaunchAnimator mActivityLaunchAnimator;
- private FakeSystemClock mSystemClock = new FakeSystemClock();
+public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
+ private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+ mBouncerExpansionCallbackCaptor;
+ private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
- private UdfpsKeyguardViewController mController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
- private List<ShadeExpansionListener> mExpansionListeners;
-
- @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
- mAltAuthInterceptorCaptor;
- private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
-
- @Captor private ArgumentCaptor<KeyguardStateController.Callback>
- mKeyguardStateControllerCallbackCaptor;
- private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mView.getContext()).thenReturn(mResourceContext);
- when(mResourceContext.getString(anyInt())).thenReturn("test string");
- when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
- when(mView.getUnpausedAlpha()).thenReturn(255);
- mController = new UdfpsKeyguardViewController(
- mView,
- mStatusBarStateController,
- mShadeExpansionStateManager,
- mStatusBarKeyguardViewManager,
- mKeyguardUpdateMonitor,
- mDumpManager,
- mLockscreenShadeTransitionController,
- mConfigurationController,
- mSystemClock,
- mKeyguardStateController,
- mUnlockedScreenOffAnimationController,
- mDialogManager,
- mUdfpsController,
- mActivityLaunchAnimator);
+ @Override
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
}
@Test
+ public void testShouldPauseAuth_bouncerShowing() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ captureBouncerExpansionCallback();
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+
+
+ @Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
captureStatusBarExpansionListeners();
@@ -202,20 +129,6 @@
}
@Test
- public void testShouldPauseAuthBouncerShowing() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- captureAltAuthInterceptor();
- when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
- mAltAuthInterceptor.onBouncerVisibilityChanged();
-
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
public void testShouldPauseAuthUnpausedAlpha0() {
mController.onViewAttached();
captureStatusBarStateListeners();
@@ -503,41 +416,8 @@
verify(mView, atLeastOnce()).setPauseAuth(false);
}
- private void sendStatusBarStateChanged(int statusBarState) {
- mStatusBarStateListener.onStateChanged(statusBarState);
- }
-
- private void captureStatusBarStateListeners() {
- verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
- mStatusBarStateListener = mStateListenerCaptor.getValue();
- }
-
- private void captureStatusBarExpansionListeners() {
- verify(mShadeExpansionStateManager, times(2))
- .addExpansionListener(mExpansionListenerCaptor.capture());
- // first (index=0) is from super class, UdfpsAnimationViewController.
- // second (index=1) is from UdfpsKeyguardViewController
- mExpansionListeners = mExpansionListenerCaptor.getAllValues();
- }
-
- private void updateStatusBarExpansion(float fraction, boolean expanded) {
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
- for (ShadeExpansionListener listener : mExpansionListeners) {
- listener.onPanelExpansionChanged(event);
- }
- }
-
- private void captureAltAuthInterceptor() {
- verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
- mAltAuthInterceptorCaptor.capture());
- mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
- }
-
- private void captureKeyguardStateControllerCallback() {
- verify(mKeyguardStateController).addCallback(
- mKeyguardStateControllerCallbackCaptor.capture());
- mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ private void captureBouncerExpansionCallback() {
+ verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
+ mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..7b19768
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
+ lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+ @Before
+ override fun setUp() {
+ allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
+ MockitoAnnotations.initMocks(this)
+ keyguardBouncerRepository =
+ KeyguardBouncerRepository(
+ mock(com.android.keyguard.ViewMediatorCallback::class.java),
+ mKeyguardUpdateMonitor
+ )
+ super.setUp()
+ }
+
+ override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
+ mBouncerInteractor =
+ BouncerInteractor(
+ keyguardBouncerRepository,
+ mock(BouncerView::class.java),
+ mock(Handler::class.java),
+ mKeyguardStateController,
+ mock(KeyguardSecurityModel::class.java),
+ mock(BouncerCallbackInteractor::class.java),
+ mock(FalsingCollector::class.java),
+ mock(DismissCallbackRegistry::class.java),
+ mock(KeyguardBypassController::class.java),
+ mKeyguardUpdateMonitor
+ )
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testShouldPauseAuthBouncerShowing() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN view attached and we're on the keyguard
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+ // WHEN the bouncer expansion is VISIBLE
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setVisible(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ yield()
+
+ // THEN UDFPS shouldPauseAuth == true
+ assertTrue(mController.shouldPauseAuth())
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 25bc91f..eb6e517 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -40,6 +40,7 @@
import junit.framework.Assert.assertSame
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 9481349..b811aab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -96,7 +96,6 @@
assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
}
-
@Test
public void testA11yDisablesTap() {
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
@@ -159,4 +158,11 @@
});
assertThat(mBrightLineFalsingManager.isProximityNear()).isFalse();
}
+
+ @Test
+ public void testA11yAction() {
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
+ when(mFalsingDataProvider.isA11yAction()).thenReturn(true);
+ assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
new file mode 100644
index 0000000..2c904e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.classifier
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FalsingA11yDelegateTest : SysuiTestCase() {
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var view: View
+ lateinit var underTest: FalsingA11yDelegate
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = FalsingA11yDelegate(falsingCollector)
+ }
+
+ @Test
+ fun testPerformAccessibilityAction_ACTION_CLICK() {
+ underTest.performAccessibilityAction(view, ACTION_CLICK, null)
+ verify(falsingCollector).onA11yAction()
+ }
+
+ @Test
+ fun testPerformAccessibilityAction_not_ACTION_CLICK() {
+ underTest.performAccessibilityAction(view, ACTION_LONG_CLICK, null)
+ verify(falsingCollector, never()).onA11yAction()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index fa9c41a..442bf91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -267,4 +267,10 @@
mFalsingCollector.onTouchEvent(up);
verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class));
}
+
+ @Test
+ public void testOnA11yAction() {
+ mFalsingCollector.onA11yAction();
+ verify(mFalsingDataProvider).onA11yAction();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 5dc607f..d315c2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -310,4 +310,10 @@
// an empty array.
assertThat(mDataProvider.getPriorMotionEvents()).isNotNull();
}
+
+ @Test
+ public void test_MotionEventComplete_A11yAction() {
+ mDataProvider.onA11yAction();
+ assertThat(mDataProvider.isA11yAction()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
new file mode 100644
index 0000000..49c7442
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.controls.ui
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.CustomIconCache
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsUiControllerImplTest : SysuiTestCase() {
+ @Mock lateinit var controlsController: ControlsController
+ @Mock lateinit var controlsListingController: ControlsListingController
+ @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
+ @Mock lateinit var activityStarter: ActivityStarter
+ @Mock lateinit var shadeController: ShadeController
+ @Mock lateinit var iconCache: CustomIconCache
+ @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
+ @Mock lateinit var keyguardStateController: KeyguardStateController
+ @Mock lateinit var userFileManager: UserFileManager
+ @Mock lateinit var userTracker: UserTracker
+ val sharedPreferences = FakeSharedPreferences()
+
+ var uiExecutor = FakeExecutor(FakeSystemClock())
+ var bgExecutor = FakeExecutor(FakeSystemClock())
+ lateinit var underTest: ControlsUiControllerImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ ControlsUiControllerImpl(
+ Lazy { controlsController },
+ context,
+ uiExecutor,
+ bgExecutor,
+ Lazy { controlsListingController },
+ controlActionCoordinator,
+ activityStarter,
+ shadeController,
+ iconCache,
+ controlsMetricsLogger,
+ keyguardStateController,
+ userFileManager,
+ userTracker
+ )
+ `when`(
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ 0,
+ 0
+ )
+ )
+ .thenReturn(sharedPreferences)
+ `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+ .thenReturn(sharedPreferences)
+ `when`(userTracker.userId).thenReturn(0)
+ }
+
+ @Test
+ fun testGetPreferredStructure() {
+ val structureInfo = mock(StructureInfo::class.java)
+ underTest.getPreferredStructure(listOf(structureInfo))
+ verify(userFileManager, times(2))
+ .getSharedPreferences(
+ fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ mode = 0,
+ userId = 0
+ )
+ }
+
+ @Test
+ fun testGetPreferredStructure_differentUserId() {
+ val structureInfo =
+ listOf(
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()),
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList()),
+ )
+ sharedPreferences
+ .edit()
+ .putString("controls_component", structureInfo[0].componentName.flattenToString())
+ .putString("controls_structure", structureInfo[0].structure.toString())
+ .commit()
+
+ val differentSharedPreferences = FakeSharedPreferences()
+ differentSharedPreferences
+ .edit()
+ .putString("controls_component", structureInfo[1].componentName.flattenToString())
+ .putString("controls_structure", structureInfo[1].structure.toString())
+ .commit()
+
+ val previousPreferredStructure = underTest.getPreferredStructure(structureInfo)
+
+ `when`(
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ 0,
+ 1
+ )
+ )
+ .thenReturn(differentSharedPreferences)
+ `when`(userTracker.userId).thenReturn(1)
+
+ val currentPreferredStructure = underTest.getPreferredStructure(structureInfo)
+
+ assertThat(previousPreferredStructure).isEqualTo(structureInfo[0])
+ assertThat(currentPreferredStructure).isEqualTo(structureInfo[1])
+ assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
index e6c8dd8..5743b2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -27,8 +27,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -57,6 +57,7 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@@ -86,6 +87,7 @@
)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
`when`(repository.show.value).thenReturn(null)
+ `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
}
@Test
@@ -97,7 +99,7 @@
verify(repository).setHide(false)
verify(repository).setStartingToHide(false)
verify(repository).setScrimmed(true)
- verify(repository).setExpansion(EXPANSION_VISIBLE)
+ verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
verify(repository).setShowingSoon(true)
verify(keyguardStateController).notifyBouncerShowing(true)
verify(bouncerCallbackInteractor).dispatchStartingToShow()
@@ -108,7 +110,7 @@
@Test
fun testShow_isNotScrimmed() {
- verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+ verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
}
@Test
@@ -124,7 +126,6 @@
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setShowingSoon(false)
- verify(repository).setOnDismissAction(null)
verify(repository).setVisible(false)
verify(repository).setHide(true)
verify(repository).setShow(null)
@@ -132,26 +133,26 @@
@Test
fun testExpansion() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
- bouncerInteractor.setExpansion(0.6f)
- verify(repository).setExpansion(0.6f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ bouncerInteractor.setPanelExpansion(0.6f)
+ verify(repository).setPanelExpansion(0.6f)
verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@Test
fun testExpansion_fullyShown() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(bouncerCallbackInteractor).dispatchFullyShown()
}
@Test
fun testExpansion_fullyHidden() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+ bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setVisible(false)
verify(repository).setShow(null)
verify(falsingCollector).onBouncerHidden()
@@ -161,8 +162,8 @@
@Test
fun testExpansion_startingToHide() {
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- bouncerInteractor.setExpansion(0.1f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(0.1f)
verify(repository).setStartingToHide(true)
verify(bouncerCallbackInteractor).dispatchStartingToHide()
}
@@ -178,8 +179,7 @@
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
- verify(repository)
- .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
@@ -234,7 +234,7 @@
@Test
fun testIsFullShowing() {
`when`(repository.isVisible.value).thenReturn(true)
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
assertThat(bouncerInteractor.isFullyShowing()).isTrue()
`when`(repository.isVisible.value).thenReturn(false)
@@ -255,7 +255,7 @@
assertThat(bouncerInteractor.isInTransit()).isTrue()
`when`(repository.showingSoon.value).thenReturn(false)
assertThat(bouncerInteractor.isInTransit()).isFalse()
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
assertThat(bouncerInteractor.isInTransit()).isTrue()
}
@@ -269,10 +269,9 @@
@Test
fun testWillDismissWithAction() {
- `when`(repository.onDismissAction.value?.onDismissAction)
- .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
- `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 6adce7a..c1fa9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -61,6 +61,7 @@
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -201,6 +202,8 @@
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private Resources mResources;
+ @Mock
+ private ViewRootImpl mViewRootImpl;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -227,6 +230,7 @@
when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
.thenReturn(mContext);
when(mNavigationBarView.getResources()).thenReturn(mResources);
+ when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
index 99a17a6..9115ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
@@ -24,6 +24,7 @@
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
@@ -48,6 +49,7 @@
public void setUp() throws Exception {
mTestableLooper = TestableLooper.get(this);
LayoutInflater inflater = LayoutInflater.from(mContext);
+ mContext.ensureTestableResources();
mTestableLooper.runWithLooper(() ->
mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
@@ -119,4 +121,30 @@
mQSCarrier.updateState(c, true);
assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
}
+
+ @Test
+ public void testCarrierNameMaxWidth_smallScreen_fromResource() {
+ int maxEms = 10;
+ mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_large_screen_shade_header, false);
+ TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+ mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+ assertEquals(maxEms, carrierText.getMaxEms());
+ }
+
+ @Test
+ public void testCarrierNameMaxWidth_largeScreen_maxInt() {
+ int maxEms = 10;
+ mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_large_screen_shade_header, true);
+ TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+ mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+ assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 760bb9b..081a218 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -47,7 +47,11 @@
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,6 +63,7 @@
@RunWithLooper
class FooterActionsViewModelTest : SysuiTestCase() {
private lateinit var utils: FooterActionsTestUtils
+ private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler())
@Before
fun setUp() {
@@ -130,6 +135,7 @@
showPowerButton = false,
footerActionsInteractor =
utils.footerActionsInteractor(
+ bgDispatcher = testDispatcher,
userSwitcherRepository =
utils.userSwitcherRepository(
userTracker = userTracker,
@@ -137,6 +143,7 @@
userManager = userManager,
userInfoController = userInfoController,
userSwitcherController = userSwitcherControllerWrapper.controller,
+ bgDispatcher = testDispatcher,
),
)
)
@@ -217,9 +224,11 @@
footerActionsInteractor =
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
+ bgDispatcher = testDispatcher,
securityRepository =
utils.securityRepository(
securityController = securityController,
+ bgDispatcher = testDispatcher,
),
),
)
@@ -288,9 +297,14 @@
footerActionsInteractor =
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
- securityRepository = utils.securityRepository(securityController),
+ securityRepository =
+ utils.securityRepository(
+ securityController,
+ bgDispatcher = testDispatcher,
+ ),
foregroundServicesRepository =
utils.foregroundServicesRepository(fgsManagerController),
+ bgDispatcher = testDispatcher,
),
)
@@ -376,6 +390,7 @@
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
broadcastDispatcher = broadcastDispatcher,
+ bgDispatcher = testDispatcher,
),
)
@@ -400,4 +415,7 @@
underTest.onVisibilityChangeRequested(visible = true)
assertThat(underTest.isVisible.value).isTrue()
}
+
+ private fun runBlockingTest(block: suspend TestScope.() -> Unit) =
+ runTest(testDispatcher) { block() }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2c76be6..b067ee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -136,6 +137,20 @@
assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
}
+ @Test
+ public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+ ImageView iv = new ImageView(mContext);
+ AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getDrawable(any())).thenReturn(d);
+ when(s.icon.getInvisibleDrawable(any())).thenReturn(d);
+
+ mIconView.updateIcon(iv, s, false);
+
+ verify(d, never()).start();
+ }
+
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
return new Drawable.ConstantState() {
@Override
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 93a1243..c98c1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -129,7 +129,6 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -153,7 +152,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.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -199,7 +197,6 @@
@Mock private KeyguardBottomAreaView mKeyguardBottomArea;
@Mock private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
@Mock private KeyguardBottomAreaView mQsFrame;
- @Mock private NotificationIconAreaController mNotificationAreaController;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationShelfController mNotificationShelfController;
@Mock private KeyguardStatusBarView mKeyguardStatusBar;
@@ -227,7 +224,7 @@
@Mock private Resources mResources;
@Mock private Configuration mConfiguration;
@Mock private KeyguardClockSwitch mKeyguardClockSwitch;
- @Mock private MediaHierarchyManager mMediaHiearchyManager;
+ @Mock private MediaHierarchyManager mMediaHierarchyManager;
@Mock private ConversationNotificationManager mConversationNotificationManager;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@@ -254,7 +251,6 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockIconViewController mLockIconViewController;
@Mock private KeyguardMediaController mKeyguardMediaController;
- @Mock private PrivacyDotViewController mPrivacyDotViewController;
@Mock private NavigationModeController mNavigationModeController;
@Mock private NavigationBarController mNavigationBarController;
@Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
@@ -294,7 +290,7 @@
private ConfigurationController mConfigurationController;
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
- private View.AccessibilityDelegate mAccessibiltyDelegate;
+ private View.AccessibilityDelegate mAccessibilityDelegate;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
private Handler mMainHandler;
@@ -456,7 +452,7 @@
mShadeLog,
mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
- mConversationNotificationManager, mMediaHiearchyManager,
+ mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
mNotificationsQSContainerController,
mNotificationStackScrollLayoutController,
@@ -465,7 +461,6 @@
mKeyguardUserSwitcherComponentFactory,
mKeyguardStatusBarViewComponentFactory,
mLockscreenShadeTransitionController,
- mNotificationAreaController,
mAuthController,
mScrimController,
mUserManager,
@@ -474,7 +469,6 @@
mAmbientState,
mLockIconViewController,
mKeyguardMediaController,
- mPrivacyDotViewController,
mTapAgainViewController,
mNavigationModeController,
mNavigationBarController,
@@ -492,6 +486,7 @@
mSysUiState,
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
+ mKeyguardIndicationController,
mNotificationListContainer,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
@@ -505,8 +500,6 @@
() -> {},
mNotificationShelfController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
verify(mView, atLeast(1)).addOnAttachStateChangeListener(
@@ -516,9 +509,9 @@
ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
- mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
+ mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue();
mNotificationPanelViewController.getStatusBarStateController()
- .addCallback(mNotificationPanelViewController.mStatusBarStateListener);
+ .addCallback(mNotificationPanelViewController.getStatusBarStateListener());
mNotificationPanelViewController
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
verify(mNotificationStackScrollLayoutController)
@@ -773,8 +766,8 @@
0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
0 /* metaState */));
- assertThat(mNotificationPanelViewController.getClosing()).isTrue();
- assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+ assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
// simulate touch that does not exceed touch slop
onTouchEvent(MotionEvent.obtain(2L /* downTime */,
@@ -788,8 +781,8 @@
0 /* metaState */));
// fling should still be called after a touch that does not exceed touch slop
- assertThat(mNotificationPanelViewController.getClosing()).isTrue();
- assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+ assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
}
@Test
@@ -844,7 +837,7 @@
@Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
- mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
+ mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
assertThat(actionList).containsAtLeastElementsIn(
@@ -856,7 +849,7 @@
@Test
public void testA11y_scrollForward() {
- mAccessibiltyDelegate.performAccessibilityAction(
+ mAccessibilityDelegate.performAccessibilityAction(
mView,
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
null);
@@ -866,7 +859,7 @@
@Test
public void testA11y_scrollUp() {
- mAccessibiltyDelegate.performAccessibilityAction(
+ mAccessibilityDelegate.performAccessibilityAction(
mView,
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
null);
@@ -1329,11 +1322,11 @@
public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
enableSplitShade(/* enabled= */ true);
mShadeExpansionStateManager.updateState(STATE_CLOSED);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
mShadeExpansionStateManager.updateState(STATE_OPENING);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isTrue();
}
@Test
@@ -1345,18 +1338,18 @@
// going to lockscreen would trigger STATE_OPENING
mShadeExpansionStateManager.updateState(STATE_OPENING);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
}
@Test
public void testQsImmediateResetsWhenPanelOpensOrCloses() {
- mNotificationPanelViewController.mQsExpandImmediate = true;
+ mNotificationPanelViewController.setQsExpandImmediate(true);
mShadeExpansionStateManager.updateState(STATE_OPEN);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
- mNotificationPanelViewController.mQsExpandImmediate = true;
+ mNotificationPanelViewController.setQsExpandImmediate(true);
mShadeExpansionStateManager.updateState(STATE_CLOSED);
- assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
}
@Test
@@ -1399,7 +1392,7 @@
@Test
public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mQsFrame.getX()).thenReturn(0f);
when(mQsFrame.getWidth()).thenReturn(1000);
when(mQsHeader.getTop()).thenReturn(0);
@@ -1419,7 +1412,7 @@
@Test
public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() {
enableSplitShade(true);
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mQsFrame.getX()).thenReturn(0f);
when(mQsFrame.getWidth()).thenReturn(1000);
when(mQsHeader.getTop()).thenReturn(0);
@@ -1495,7 +1488,7 @@
@Test
public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
setIsFullWidth(true);
@@ -1504,7 +1497,7 @@
@Test
public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
setIsFullWidth(false);
@@ -1513,7 +1506,7 @@
@Test
public void onLayoutChange_qsNotSet_doesNotCrash() {
- mNotificationPanelViewController.mQs = null;
+ mNotificationPanelViewController.setQs(null);
triggerLayoutChange();
}
@@ -1539,7 +1532,7 @@
@Test
public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() {
float squishinessFraction = 0.456f;
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
.thenReturn(squishinessFraction);
when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
@@ -1567,7 +1560,7 @@
public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() {
float lsSquishinessFraction = 0.456f;
float nsslSquishinessFraction = 0.987f;
- mNotificationPanelViewController.mQs = mQs;
+ mNotificationPanelViewController.setQs(mQs);
when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
.thenReturn(lsSquishinessFraction);
when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
@@ -1586,7 +1579,7 @@
@Test
public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
@@ -1601,7 +1594,7 @@
@Test
public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
@@ -1616,7 +1609,7 @@
@Test
public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
@@ -1631,7 +1624,7 @@
@Test
public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(true, false);
@@ -1645,7 +1638,7 @@
@Test
public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
StatusBarStateController.StateListener statusBarStateListener =
- mNotificationPanelViewController.mStatusBarStateListener;
+ mNotificationPanelViewController.getStatusBarStateListener();
statusBarStateListener.onStateChanged(SHADE_LOCKED);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
@@ -1664,11 +1657,11 @@
public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
// Given: Shade is expanded
mNotificationPanelViewController.notifyExpandingFinished();
- mNotificationPanelViewController.setIsClosing(false);
+ mNotificationPanelViewController.setClosing(false);
// When: Shade flings to close not canceled
mNotificationPanelViewController.notifyExpandingStarted();
- mNotificationPanelViewController.setIsClosing(true);
+ mNotificationPanelViewController.setClosing(true);
mNotificationPanelViewController.onFlingEnd(false);
// Then: AmbientState's mIsClosing should be set to false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 26a0770..a4a7995 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -152,7 +152,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should intercept touch
@@ -165,7 +165,7 @@
// WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we shouldn't intercept touch
@@ -178,7 +178,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 09add65..43c6942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -66,6 +66,8 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var shadeLogger: ShadeLogger
private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
private lateinit var underTest: PulsingGestureListener
@@ -81,6 +83,7 @@
centralSurfaces,
ambientDisplayConfiguration,
statusBarStateController,
+ shadeLogger,
tunerService,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 3ff7639..f96c39f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -406,6 +406,10 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupSibling1)
+
+ // In addition make sure we have explicitly marked the summary as having interrupted due
+ // to the alert being transferred
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -424,6 +428,7 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupChild1)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -449,6 +454,7 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -474,6 +480,7 @@
verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
verify(mHeadsUpManager).showNotification(mGroupChild1)
verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -512,6 +519,7 @@
verify(mHeadsUpManager).showNotification(mGroupPriority)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -548,6 +556,7 @@
verify(mHeadsUpManager).showNotification(mGroupPriority)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -582,6 +591,7 @@
verify(mHeadsUpManager).showNotification(mGroupPriority)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ assertTrue(mGroupSummary.hasInterrupted())
}
@Test
@@ -672,6 +682,35 @@
}
@Test
+ fun testNoTransfer_groupSummaryNotAlerting() {
+ // When we have a group where the summary should not alert and exactly one child should
+ // alert, we should never mark the group summary as interrupted (because it doesn't).
+ setShouldHeadsUp(mGroupSummary, false)
+ setShouldHeadsUp(mGroupChild1, true)
+ setShouldHeadsUp(mGroupChild2, false)
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mCollectionListener.onEntryAdded(mGroupChild2)
+ val groupEntry = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupChild1, mGroupChild2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupChild1)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupChild1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ assertFalse(mGroupSummary.hasInterrupted())
+ }
+
+ @Test
fun testOnRankingApplied_newEntryShouldAlert() {
// GIVEN that mEntry has never interrupted in the past, and now should
// and is new enough to do so
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 421f918..7478e4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -22,6 +22,7 @@
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -60,6 +61,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -194,6 +196,25 @@
}
/**
+ * Creates a generic row with rounded border.
+ *
+ * @return a generic row with the set roundness.
+ * @throws Exception
+ */
+ public ExpandableNotificationRow createRowWithRoundness(
+ float topRoundness,
+ float bottomRoundness,
+ SourceType sourceType
+ ) throws Exception {
+ ExpandableNotificationRow row = createRow();
+ row.requestTopRoundness(topRoundness, false, sourceType);
+ row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+ assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
+ assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
+ return row;
+ }
+
+ /**
* Creates a generic row.
*
* @return a generic row with no special properties.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 7c41abba..438b528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,6 +25,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -151,4 +152,37 @@
Assert.assertNotNull("Children container must have a header after recreation",
mChildrenContainer.getCurrentHeaderView());
}
+
+ @Test
+ public void addNotification_shouldResetOnScrollRoundness() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnScroll);
+
+ mChildrenContainer.addNotification(row, 0);
+
+ Assert.assertEquals(0f, row.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(0f, row.getBottomRoundness(), /* delta = */ 0f);
+ }
+
+ @Test
+ public void addNotification_shouldNotResetOtherRoundness() throws Exception {
+ ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.DefaultValue);
+ ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnDismissAnimation);
+
+ mChildrenContainer.addNotification(row1, 0);
+ mChildrenContainer.addNotification(row2, 0);
+
+ Assert.assertEquals(1f, row1.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row1.getBottomRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row2.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row2.getBottomRoundness(), /* delta = */ 0f);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 7741813..bda2336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.notification.stack
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
@@ -8,8 +9,10 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -37,6 +40,13 @@
private val shelfState = shelf.viewState as NotificationShelf.ShelfState
private val ambientState = mock(AmbientState::class.java)
private val hostLayoutController: NotificationStackScrollLayoutController = mock()
+ private val notificationTestHelper by lazy {
+ allowTestableLooperAsMainThread()
+ NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this))
+ }
@Before
fun setUp() {
@@ -299,6 +309,39 @@
)
}
+ @Test
+ fun resetOnScrollRoundness_shouldSetOnScrollTo0() {
+ val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnScroll)
+
+ NotificationShelf.resetOnScrollRoundness(row)
+
+ assertEquals(0f, row.topRoundness)
+ assertEquals(0f, row.bottomRoundness)
+ }
+
+ @Test
+ fun resetOnScrollRoundness_shouldNotResetOtherRoundness() {
+ val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.DefaultValue)
+ val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnDismissAnimation)
+
+ NotificationShelf.resetOnScrollRoundness(row1)
+ NotificationShelf.resetOnScrollRoundness(row2)
+
+ assertEquals(1f, row1.topRoundness)
+ assertEquals(1f, row1.bottomRoundness)
+ assertEquals(1f, row2.topRoundness)
+ assertEquals(1f, row2.bottomRoundness)
+ }
+
private fun setFractionToShade(fraction: Float) {
whenever(ambientState.fractionToShade).thenReturn(fraction)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5755782..7ce3a67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -500,7 +500,7 @@
mKeyguardVieMediatorCallback);
// TODO: we should be able to call mCentralSurfaces.start() and have all the below values
- // initialized automatically.
+ // initialized automatically and make NPVC private.
mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController;
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0c35659..7166666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -307,7 +307,7 @@
@Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+ when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
expansionEvent(
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
@@ -361,7 +361,7 @@
@Test
public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+ when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
mStatusBarKeyguardViewManager.show(null);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index de1fec8..288f54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,16 +17,18 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileConnectionRepository : MobileConnectionRepository {
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
- override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+ override val subscriptionModelFlow = _subscriptionsModelFlow
private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled
+ private val _isDefaultDataSubscription = MutableStateFlow(true)
+ override val isDefaultDataSubscription = _isDefaultDataSubscription
+
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
_subscriptionsModelFlow.value = model
}
@@ -34,4 +36,8 @@
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
+
+ fun setIsDefaultDataSubscription(isDefault: Boolean) {
+ _isDefaultDataSubscription.value = isDefault
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 813e750..533d5d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -26,18 +27,26 @@
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
- private val _activeMobileDataSubscriptionId =
- MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
+ private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+ override val defaultDataSubId = _defaultDataSubId
+
+ private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
+ override val defaultMobileNetworkConnectivity = _mobileConnectivity
+
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
}
+ private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+ override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
+
fun setSubscriptions(subs: List<SubscriptionInfo>) {
_subscriptionsFlow.value = subs
}
@@ -46,6 +55,18 @@
_defaultDataSubRatConfig.value = config
}
+ fun setDefaultDataSubId(id: Int) {
+ _defaultDataSubId.value = id
+ }
+
+ fun setMobileConnectivity(model: MobileConnectivityModel) {
+ _mobileConnectivity.value = model
+ }
+
+ suspend fun triggerGlobalMobileDataSettingChangedEvent() {
+ _globalMobileDataSettingChangedEvent.emit(Unit)
+ }
+
fun setActiveMobileDataSubscriptionId(subId: Int) {
_activeMobileDataSubscriptionId.value = subId
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
index 6c495c5..141b50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -16,13 +16,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
/** Defaults to `true` */
class FakeUserSetupRepository : UserSetupRepository {
private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+ override val isUserSetupFlow = _isUserSetup
fun setUserSetup(setup: Boolean) {
_isUserSetup.value = setup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 0939364..5ce51bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.os.UserHandle
+import android.provider.Settings
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
@@ -42,6 +44,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -67,16 +70,23 @@
@Mock private lateinit var logger: ConnectivityPipelineLogger
private val scope = CoroutineScope(IMMEDIATE)
+ private val globalSettings = FakeSettings()
+ private val connectionsRepo = FakeMobileConnectionsRepository()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ globalSettings.userId = UserHandle.USER_ALL
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
underTest =
MobileConnectionRepositoryImpl(
+ context,
SUB_1_ID,
telephonyManager,
+ globalSettings,
+ connectionsRepo.defaultDataSubId,
+ connectionsRepo.globalMobileDataSettingChangedEvent,
IMMEDIATE,
logger,
scope,
@@ -290,14 +300,20 @@
}
@Test
- fun dataEnabled_isEnabled() =
+ fun dataEnabled_initial_false() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
- var latest: Boolean? = null
- val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+ assertThat(underTest.dataEnabled.value).isFalse()
+ }
- assertThat(latest).isTrue()
+ @Test
+ fun dataEnabled_isEnabled_true() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+ val job = underTest.dataEnabled.launchIn(this)
+
+ assertThat(underTest.dataEnabled.value).isTrue()
job.cancel()
}
@@ -306,10 +322,59 @@
fun dataEnabled_isDisabled() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ val job = underTest.dataEnabled.launchIn(this)
+
+ assertThat(underTest.dataEnabled.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDefaultDataSubscription_isDefault() =
+ runBlocking(IMMEDIATE) {
+ connectionsRepo.setDefaultDataSubId(SUB_1_ID)
+
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDefaultDataSubscription_isNotDefault() =
+ runBlocking(IMMEDIATE) {
+ // Our subId is SUB_1_ID
+ connectionsRepo.setDefaultDataSubId(123)
+
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+ // We don't read the setting directly, we query telephony when changes happen
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ globalSettings.putInt(subIdSettingName, 0)
+ assertThat(latest).isFalse()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+ globalSettings.putInt(subIdSettingName, 1)
+ assertThat(latest).isTrue()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ globalSettings.putInt(subIdSettingName, 0)
assertThat(latest).isFalse()
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
index 326e0d281..a953a3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -16,26 +16,33 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
+import com.android.internal.telephony.PhoneConstants
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
@@ -43,7 +50,6 @@
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -54,32 +60,26 @@
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
+ @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
private val scope = CoroutineScope(IMMEDIATE)
+ private val globalSettings = FakeSettings()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(
- broadcastDispatcher.broadcastFlow(
- any(),
- nullable(),
- ArgumentMatchers.anyInt(),
- nullable(),
- )
- )
- .thenReturn(flowOf(Unit))
underTest =
MobileConnectionsRepositoryImpl(
+ connectivityManager,
subscriptionManager,
telephonyManager,
logger,
- broadcastDispatcher,
+ fakeBroadcastDispatcher,
+ globalSettings,
context,
IMMEDIATE,
scope,
@@ -214,6 +214,139 @@
job.cancel()
}
+ @Test
+ fun testDefaultDataSubId_updatesOnBroadcast() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+ )
+ }
+
+ assertThat(latest).isEqualTo(SUB_2_ID)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ )
+ }
+
+ assertThat(latest).isEqualTo(SUB_1_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_default() {
+ assertThat(underTest.defaultMobileNetworkConnectivity.value)
+ .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+ }
+
+ @Test
+ fun mobileConnectivity_isConnected_isValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = true, validated = true)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true))
+
+ job.cancel()
+ }
+
+ @Test
+ fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() =
+ runBlocking(IMMEDIATE) {
+ var produced = false
+ val job =
+ underTest.globalMobileDataSettingChangedEvent
+ .onEach { produced = true }
+ .launchIn(this)
+
+ assertThat(produced).isFalse()
+
+ globalSettings.putInt(Settings.Global.MOBILE_DATA, 0)
+
+ assertThat(produced).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_isConnected_isNotValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = true, validated = false)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false))
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_isNotConnected_isNotValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = false, validated = false)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+
+ job.cancel()
+ }
+
+ /** In practice, I don't think this state can ever happen (!connected, validated) */
+ @Test
+ fun mobileConnectivity_isNotConnected_isValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = false, validated = true)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isEqualTo(MobileConnectivityModel(false, true))
+
+ job.cancel()
+ }
+
+ private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
+ whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated)
+ }
+
+ private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+ verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
verify(subscriptionManager)
@@ -242,5 +375,8 @@
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+ private const val NET_ID = 123
+ private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 5611c44..3ae7d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -28,18 +28,23 @@
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly
+ private val _isFailedConnection = MutableStateFlow(false)
+ override val isDefaultConnectionFailed = _isFailedConnection
+
+ override val isDataConnected = MutableStateFlow(true)
+
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
+ private val _isDefaultDataEnabled = MutableStateFlow(true)
+ override val isDefaultDataEnabled = _isDefaultDataEnabled
+
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val level = _level
private val _numberOfLevels = MutableStateFlow(4)
override val numberOfLevels = _numberOfLevels
- private val _cutOut = MutableStateFlow(false)
- override val cutOut = _cutOut
-
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
_iconGroup.value = group
}
@@ -52,6 +57,14 @@
_isDataEnabled.value = enabled
}
+ fun setIsDefaultDataEnabled(disabled: Boolean) {
+ _isDefaultDataEnabled.value = disabled
+ }
+
+ fun setIsFailedConnection(failed: Boolean) {
+ _isFailedConnection.value = failed
+ }
+
fun setLevel(level: Int) {
_level.value = level
}
@@ -59,8 +72,4 @@
fun setNumberOfLevels(num: Int) {
_numberOfLevels.value = num
}
-
- fun setCutOut(cutOut: Boolean) {
- _cutOut.value = cutOut
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 2bd2286..061c3b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -26,8 +26,7 @@
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
- MobileIconsInteractor {
+class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -46,9 +45,14 @@
FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
)
+ override val isDefaultConnectionFailed = MutableStateFlow(false)
+
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val filteredSubscriptions = _filteredSubscriptions
+ private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
+ override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index ff44af4..7fc1c0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -34,6 +35,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -49,12 +51,17 @@
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
private val connectionRepository = FakeMobileConnectionRepository()
+ private val scope = CoroutineScope(IMMEDIATE)
+
@Before
fun setUp() {
underTest =
MobileIconInteractorImpl(
+ scope,
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled,
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
+ mobileIconsInteractor.isDefaultConnectionFailed,
mobileMappingsProxy,
connectionRepository,
)
@@ -196,6 +203,66 @@
job.cancel()
}
+ @Test
+ fun test_isDefaultDataEnabled_matchesParent() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun test_isDefaultConnectionFailed_matchedParent() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isDefaultConnectionFailed.launchIn(this)
+
+ mobileIconsInteractor.isDefaultConnectionFailed.value = false
+ assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
+
+ mobileIconsInteractor.isDefaultConnectionFailed.value = true
+ assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_connected() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
+ )
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_notConnected() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
+ )
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 877ce0e..b56dcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
@@ -32,6 +34,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -168,6 +171,92 @@
job.cancel()
}
+ @Test
+ fun activeDataConnection_turnedOn() =
+ runBlocking(IMMEDIATE) {
+ CONNECTION_1.setDataEnabled(true)
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeDataConnection_turnedOff() =
+ runBlocking(IMMEDIATE) {
+ CONNECTION_1.setDataEnabled(true)
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ CONNECTION_1.setDataEnabled(false)
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeDataConnection_invalidSubId() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
+ yield()
+
+ // An invalid active subId should tell us that data is off
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_connected_validated_notFailed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_notConnected_notValidated_notFailed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_connected_notValidated_failed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false))
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index ce0f33f..d4c2c3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -46,10 +46,12 @@
MockitoAnnotations.initMocks(this)
interactor.apply {
setLevel(1)
- setCutOut(false)
+ setIsDefaultDataEnabled(true)
+ setIsFailedConnection(false)
setIconGroup(THREE_G)
setIsEmergencyOnly(false)
setNumberOfLevels(4)
+ isDataConnected.value = true
}
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
}
@@ -59,8 +61,23 @@
runBlocking(IMMEDIATE) {
var latest: Int? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ val expected = defaultSignal()
- assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconId_cutout_whenDefaultDataDisabled() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIsDefaultDataEnabled(false)
+
+ var latest: Int? = null
+ val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ val expected = defaultSignal(level = 1, connected = false)
+
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -97,6 +114,44 @@
}
@Test
+ fun networkType_nullWhenFailedConnection() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsDataEnabled(true)
+ interactor.setIsFailedConnection(true)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_nullWhenDataDisconnects() =
+ runBlocking(IMMEDIATE) {
+ val initial =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+
+ interactor.setIconGroup(THREE_G)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.setIconGroup(THREE_G)
+ assertThat(latest).isEqualTo(initial)
+
+ interactor.isDataConnected.value = false
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
fun networkType_null_changeToDisabled() =
runBlocking(IMMEDIATE) {
val expected =
@@ -119,6 +174,14 @@
job.cancel()
}
+ /** Convenience constructor for these tests */
+ private fun defaultSignal(
+ level: Int = 1,
+ connected: Boolean = true,
+ ): Int {
+ return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index 97571b2..f682e31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -44,6 +44,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceUntilIdle
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 252dcfc..4430bb4b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -254,11 +255,13 @@
private boolean mSafeMode;
private int mMaxWidgetBitmapMemory;
private boolean mIsProviderInfoPersisted;
+ private boolean mIsCombinedBroadcastEnabled;
AppWidgetServiceImpl(Context context) {
mContext = context;
}
+ @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)
public void onStart() {
mPackageManager = AppGlobals.getPackageManager();
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
@@ -277,6 +280,8 @@
mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
&& DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
+ mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
Slog.d(TAG, "App widget provider info will not be persisted on this device");
}
@@ -1123,16 +1128,16 @@
final int widgetCount = provider.widgets.size();
if (widgetCount == 1) {
- // Tell the provider that it's ready.
- sendEnableIntentLocked(provider);
+ // If we are binding the very first widget from a provider, we will send
+ // a combined broadcast or 2 separate broadcasts to tell the provider that
+ // it's ready, and we need them to provide the update now.
+ sendEnableAndUpdateIntentLocked(provider, new int[]{appWidgetId});
+ } else {
+ // For any widget other then the first one, we just send update intent
+ // as we normally would.
+ sendUpdateIntentLocked(provider, new int[]{appWidgetId});
}
- // Send an update now -- We need this update now, and just for this appWidgetId.
- // It's less critical when the next one happens, so when we schedule the next one,
- // we add updatePeriodMillis to its start time. That time will have some slop,
- // but that's okay.
- sendUpdateIntentLocked(provider, new int[] {appWidgetId});
-
// Schedule the future updates.
registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
@@ -2361,6 +2366,22 @@
cancelBroadcastsLocked(provider);
}
+ private void sendEnableAndUpdateIntentLocked(@NonNull Provider p, int[] appWidgetIds) {
+ final boolean canSendCombinedBroadcast = mIsCombinedBroadcastEnabled && p.info != null
+ && p.info.isExtendedFromAppWidgetProvider;
+ if (!canSendCombinedBroadcast) {
+ // If this function is called by mistake, send two separate broadcasts instead
+ sendEnableIntentLocked(p);
+ sendUpdateIntentLocked(p, appWidgetIds);
+ return;
+ }
+
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ intent.setComponent(p.id.componentName);
+ sendBroadcastAsUser(intent, p.id.getProfile());
+ }
+
private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.id.componentName);
@@ -2852,7 +2873,6 @@
if (provider.widgets.size() > 0) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"appwidget init " + provider.id.componentName.getPackageName());
- sendEnableIntentLocked(provider);
provider.widgets.forEach(widget -> {
widget.trackingUpdate = true;
Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -2861,7 +2881,7 @@
Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
});
int[] appWidgetIds = getWidgetIds(provider.widgets);
- sendUpdateIntentLocked(provider, appWidgetIds);
+ sendEnableAndUpdateIntentLocked(provider, appWidgetIds);
registerForBroadcastsLocked(provider, appWidgetIds);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index a778b57..b8bac8d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -69,7 +69,6 @@
class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
- private static final int MESSAGE_IGNORE_AUTH = 1;
private static final int MESSAGE_AUTH_SUCCESS = 2;
private static final int MESSAGE_FINGER_UP = 3;
@NonNull
@@ -235,12 +234,6 @@
() -> {
long delay = 0;
if (authenticated && mSensorProps.isAnySidefpsType()) {
- if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
- Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0,
- true);
- return;
- }
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
if (mSideFpsLastAcquireStartTime != -1) {
@@ -497,16 +490,12 @@
if (mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): onPowerPressed");
mHandler.post(() -> {
- if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
- Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
- mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
- // Do not call onError() as that will send an additional callback to coex.
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
- }
- mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
- mHandler.postDelayed(() -> {
- }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+ Slog.i(TAG, "(sideFPS): finishing auth");
+ // Ignore auths after a power has been detected
+ mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+ // Do not call onError() as that will send an additional callback to coex.
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ mSensorOverlays.hide(getSensorId());
});
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2eb2cf6..e86f34b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -812,7 +812,6 @@
StartingData mStartingData;
WindowState mStartingWindow;
StartingSurfaceController.StartingSurface mStartingSurface;
- boolean startingDisplayed;
boolean startingMoved;
/** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@
/** Whether the input to this activity will be dropped during the current playing animation. */
private boolean mIsInputDroppedForAnimation;
- /**
- * If it is non-null, it requires all activities who have the same starting data to be drawn
- * to remove the starting window.
- * TODO(b/189385912): Remove starting window related fields after migrating them to task.
- */
- private StartingData mSharedStartingData;
-
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1200,14 +1192,11 @@
pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
pw.print(" mIsExiting="); pw.println(mIsExiting);
}
- if (mSharedStartingData != null) {
- pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
- }
- if (mStartingWindow != null || mStartingSurface != null
- || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+ if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+ || startingMoved || mVisibleSetFromTransferredStartingWindow) {
pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
pw.print(" startingSurface="); pw.print(mStartingSurface);
- pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+ pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
pw.print(" startingMoved="); pw.print(startingMoved);
pw.println(" mVisibleSetFromTransferredStartingWindow="
+ mVisibleSetFromTransferredStartingWindow);
@@ -2690,13 +2679,23 @@
}
}
+ boolean isStartingWindowDisplayed() {
+ final StartingData data = mStartingData != null ? mStartingData : task != null
+ ? task.mSharedStartingData : null;
+ return data != null && data.mIsDisplayed;
+ }
+
/** Called when the starting window is added to this activity. */
void attachStartingWindow(@NonNull WindowState startingWindow) {
startingWindow.mStartingData = mStartingData;
mStartingWindow = startingWindow;
- // The snapshot type may have called associateStartingDataWithTask().
- if (mStartingData != null && mStartingData.mAssociatedTask != null) {
- attachStartingSurfaceToAssociatedTask();
+ if (mStartingData != null) {
+ if (mStartingData.mAssociatedTask != null) {
+ // The snapshot type may have called associateStartingDataWithTask().
+ attachStartingSurfaceToAssociatedTask();
+ } else if (isEmbedded()) {
+ associateStartingWindowWithTaskIfNeeded();
+ }
}
}
@@ -2711,11 +2710,7 @@
/** Called when the starting window is not added yet but its data is known to fill the task. */
private void associateStartingDataWithTask() {
mStartingData.mAssociatedTask = task;
- task.forAllActivities(r -> {
- if (r.mVisibleRequested && !r.firstWindowDrawn) {
- r.mSharedStartingData = mStartingData;
- }
- });
+ task.mSharedStartingData = mStartingData;
}
/** Associates and attaches an added starting window to the current task. */
@@ -2746,10 +2741,8 @@
void removeStartingWindowAnimation(boolean prepareAnimation) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
- if (mSharedStartingData != null) {
- mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
- r.mSharedStartingData = null;
- });
+ if (task != null) {
+ task.mSharedStartingData = null;
}
if (mStartingWindow == null) {
if (mStartingData != null) {
@@ -2772,7 +2765,6 @@
mStartingData = null;
mStartingSurface = null;
mStartingWindow = null;
- startingDisplayed = false;
if (surface == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
+ "startingSurface==null, couldn't remove");
@@ -4257,7 +4249,7 @@
* @return {@code true} if starting window is in app's hierarchy.
*/
boolean hasStartingWindow() {
- if (startingDisplayed || mStartingData != null) {
+ if (mStartingData != null) {
return true;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4355,10 +4347,7 @@
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
mStartingSurface = fromActivity.mStartingSurface;
- startingDisplayed = fromActivity.startingDisplayed;
- fromActivity.startingDisplayed = false;
mStartingWindow = tStartingWindow;
reportedVisible = fromActivity.reportedVisible;
fromActivity.mStartingData = null;
@@ -4424,7 +4413,6 @@
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Moving pending starting from %s to %s", fromActivity, this);
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
fromActivity.mStartingData = null;
fromActivity.startingMoved = true;
scheduleAddStartingWindow();
@@ -6534,14 +6522,11 @@
// Remove starting window directly if is in a pure task. Otherwise if it is associated with
// a task (e.g. nested task fragment), then remove only if all visible windows in the task
// are drawn.
- final Task associatedTask =
- mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+ final Task associatedTask = task.mSharedStartingData != null ? task : null;
if (associatedTask == null) {
removeStartingWindow();
- } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
- // Don't block starting window removal if an Activity can't be a starting window
- // target.
- && r.mSharedStartingData != null) == null) {
+ } else if (associatedTask.getActivity(
+ r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -6756,7 +6741,6 @@
if (mLastTransactionSequence != mWmService.mTransactionSequence) {
mLastTransactionSequence = mWmService.mTransactionSequence;
mNumDrawnWindows = 0;
- startingDisplayed = false;
// There is the main base application window, even if it is exiting, wait for it
mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6800,9 +6784,9 @@
isInterestingAndDrawn = true;
}
}
- } else if (w.isDrawn()) {
+ } else if (mStartingData != null && w.isDrawn()) {
// The starting window for this container is drawn.
- startingDisplayed = true;
+ mStartingData.mIsDisplayed = true;
}
}
@@ -7550,7 +7534,8 @@
ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
+ ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
- this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+ this, reportedVisible, okToDisplay(), okToAnimate(),
+ isStartingWindowDisplayed());
// clean up thumbnail window
if (mThumbnail != null) {
@@ -9649,7 +9634,7 @@
if (mStartingWindow != null) {
mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
}
- proto.write(STARTING_DISPLAYED, startingDisplayed);
+ proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
proto.write(STARTING_MOVED, startingMoved);
proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 9c95e31..671524b 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1229,11 +1229,11 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.startingDisplayed,
+ activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+ if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
return false;
}
if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index e7ab63e..13a1cb6 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -216,14 +216,10 @@
return;
}
- if (container != null) {
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- } else {
- t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
- }
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
t.setAlpha(d.mDimLayer, alpha);
t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
@@ -231,32 +227,6 @@
}
/**
- * Finish a dim started by dimAbove in the case there was no call to dimAbove.
- *
- * @param t A Transaction in which to finish the dim.
- */
- void stopDim(SurfaceControl.Transaction t) {
- if (mDimState != null) {
- t.hide(mDimState.mDimLayer);
- mDimState.isVisible = false;
- mDimState.mDontReset = false;
- }
- }
-
- /**
- * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
- * remove this effect. If the Dim can be assosciated with a particular child of the host
- * consider using the other variant of dimAbove which ties the Dim lifetime to the child
- * lifetime more explicitly.
- *
- * @param t A transaction in which to apply the Dim.
- * @param alpha The alpha at which to Dim.
- */
- void dimAbove(SurfaceControl.Transaction t, float alpha) {
- dim(t, null, 1, alpha, 0);
- }
-
- /**
* Place a dim above the given container, which should be a child of the host container.
* for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
* and the child should call dimAbove again to request the Dim to continue.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 42a3ec6..2688ff7 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -81,11 +81,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -1439,90 +1435,6 @@
*/
int selectAnimation(WindowState win, int transit) {
ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);
- if (win == mStatusBar) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_top_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_top_enter;
- }
- } else if (win == mNavigationBar) {
- if (win.getAttrs().windowAnimations != 0) {
- return ANIMATION_STYLEABLE;
- }
- // This can be on either the bottom or the right or the left.
- if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
- return R.anim.dock_bottom_exit_keyguard;
- } else {
- return R.anim.dock_bottom_exit;
- }
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_bottom_enter;
- }
- } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_right_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_right_enter;
- }
- } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_left_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_left_enter;
- }
- }
- } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
- || win == mExtraNavBarAlt) {
- if (win.getAttrs().windowAnimations != 0) {
- return ANIMATION_STYLEABLE;
- }
-
- int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition;
-
- boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE;
- boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW;
-
- switch (pos) {
- case ALT_BAR_LEFT:
- if (isExitOrHide) {
- return R.anim.dock_left_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_left_enter;
- }
- break;
- case ALT_BAR_RIGHT:
- if (isExitOrHide) {
- return R.anim.dock_right_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_right_enter;
- }
- break;
- case ALT_BAR_BOTTOM:
- if (isExitOrHide) {
- return R.anim.dock_bottom_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_bottom_enter;
- }
- break;
- case ALT_BAR_TOP:
- if (isExitOrHide) {
- return R.anim.dock_top_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_top_enter;
- }
- break;
- }
- }
if (transit == TRANSIT_PREVIEW_DONE) {
if (win.hasAppShownWindows()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a8d13c5..eaa08fd 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1578,7 +1578,9 @@
false /* forceRelayout */);
} else {
// Revert the rotation to our saved value if we transition from HALF_FOLDED.
- mRotation = mHalfFoldSavedRotation;
+ if (mHalfFoldSavedRotation != -1) {
+ mRotation = mHalfFoldSavedRotation;
+ }
// Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
// so we will override USER_ROTATION_LOCKED and allow a rotation).
mService.updateRotation(false /* alwaysSendConfiguration */,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index f11c2a7..dcb7fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -604,7 +604,10 @@
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
}
if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(false /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(false /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
} catch (RemoteException ex) {
@@ -619,7 +622,10 @@
void showLockTaskToast() {
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
try {
- getStatusBarService().showPinningEscapeToast();
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEscapeToast();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to send pinning escape toast", e);
}
@@ -727,7 +733,10 @@
// When lock task starts, we disable the status bars.
try {
if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(true /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(true /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(lockTaskModeState);
mLockTaskModeState = lockTaskModeState;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343..300a894 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@
*/
Task mAssociatedTask;
+ /** Whether the starting window is drawn. */
+ boolean mIsDisplayed;
+
protected StartingData(WindowManagerService service, int typeParams) {
mService = service;
mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index eba49bb..66d7af9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@
int mLockTaskUid = -1; // The uid of the application that called startLockTask().
+ /**
+ * If non-null, the starting window should cover the associated task. It is assigned when the
+ * parent activity of starting window is put in a partial area of the task. This field will be
+ * cleared when all visible activities in this task are drawn.
+ */
+ StartingData mSharedStartingData;
+
/** The process that had previously hosted the root activity of this task.
* Used to know that we should try harder to keep this process around, in case the
* user wants to return to it. */
@@ -3688,6 +3695,9 @@
if (mRootProcess != null) {
pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
}
+ if (mSharedStartingData != null) {
+ pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+ }
pw.print(prefix); pw.print("taskId=" + mTaskId);
pw.println(" rootTaskId=" + getRootTaskId());
pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d178676..879c7e2 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1890,10 +1890,10 @@
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
- // There may be a trampoline activity without window on top of the existing task
- // which is moving to front. Exclude the finishing activity so the window of next
- // activity can be chosen to create the animation target.
- ? getTopNonFinishingActivity()
+ // There may be a launching (e.g. trampoline or embedded) activity without a window
+ // on top of the existing task which is moving to front. Exclude finishing activity
+ // so the window of next activity can be chosen to create the animation target.
+ ? getActivity(r -> !r.finishing && r.hasChild())
: getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 32f6197..4d29c4d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -476,6 +476,48 @@
}
/**
+ * Records that a particular container has been reparented. This only effects windows that have
+ * already been collected in the transition. This should be called before reparenting because
+ * the old parent may be removed during reparenting, for example:
+ * {@link Task#shouldRemoveSelfOnLastChildRemoval}
+ */
+ void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
+ if (!mChanges.containsKey(wc)) {
+ // #collectReparentChange() will be called when the window is reparented. Skip if it is
+ // a window that has not been collected, which means we don't care about this window for
+ // the current transition.
+ return;
+ }
+ final ChangeInfo change = mChanges.get(wc);
+ // Use the current common ancestor if there are multiple reparent, and the original parent
+ // has been detached. Otherwise, use the original parent before the transition.
+ final WindowContainer prevParent =
+ change.mStartParent == null || change.mStartParent.isAttached()
+ ? change.mStartParent
+ : change.mCommonAncestor;
+ if (prevParent == null || !prevParent.isAttached()) {
+ Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has"
+ + " been detached: " + wc);
+ return;
+ }
+ if (prevParent == newParent) {
+ Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: "
+ + wc);
+ return;
+ }
+ if (!newParent.isAttached()) {
+ Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after"
+ + " reparenting: " + wc);
+ return;
+ }
+ WindowContainer ancestor = newParent;
+ while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
+ ancestor = ancestor.getParent();
+ }
+ change.mCommonAncestor = ancestor;
+ }
+
+ /**
* @return {@code true} if `wc` is a participant or is a descendant of one.
*/
boolean isInTransition(WindowContainer wc) {
@@ -837,8 +879,8 @@
void abort() {
// This calls back into itself via controller.abort, so just early return here.
if (mState == STATE_ABORT) return;
- if (mState != STATE_COLLECTING) {
- throw new IllegalStateException("Too late to abort.");
+ if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
+ throw new IllegalStateException("Too late to abort. state=" + mState);
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
mState = STATE_ABORT;
@@ -1558,20 +1600,7 @@
return out;
}
- // Find the top-most shared ancestor of app targets.
- WindowContainer<?> ancestor = topApp.getParent();
- // Go up ancestor parent chain until all targets are descendants.
- ancestorLoop:
- while (ancestor != null) {
- for (int i = sortedTargets.size() - 1; i >= 0; --i) {
- final WindowContainer wc = sortedTargets.get(i);
- if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) {
- ancestor = ancestor.getParent();
- continue ancestorLoop;
- }
- }
- break;
- }
+ WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
// make leash based on highest (z-order) direct child of ancestor with a participant.
WindowContainer leashReference = sortedTargets.get(0);
@@ -1688,6 +1717,46 @@
return out;
}
+ /**
+ * Finds the top-most common ancestor of app targets.
+ *
+ * Makes sure that the previous parent is also a descendant to make sure the animation won't
+ * be covered by other windows below the previous parent. For example, when reparenting an
+ * activity from PiP Task to split screen Task.
+ */
+ @NonNull
+ private static WindowContainer<?> findCommonAncestor(
+ @NonNull ArrayList<WindowContainer> targets,
+ @NonNull ArrayMap<WindowContainer, ChangeInfo> changes,
+ @NonNull WindowContainer<?> topApp) {
+ WindowContainer<?> ancestor = topApp.getParent();
+ // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
+ // null because all targets are attached.
+ for (int i = targets.size() - 1; i >= 0; i--) {
+ final WindowContainer wc = targets.get(i);
+ if (isWallpaper(wc)) {
+ // Skip the non-app window.
+ continue;
+ }
+ while (!wc.isDescendantOf(ancestor)) {
+ ancestor = ancestor.getParent();
+ }
+
+ // Make sure the previous parent is also a descendant to make sure the animation won't
+ // be covered by other windows below the previous parent. For example, when reparenting
+ // an activity from PiP Task to split screen Task.
+ final ChangeInfo change = changes.get(wc);
+ final WindowContainer prevParent = change.mCommonAncestor;
+ if (prevParent == null || !prevParent.isAttached()) {
+ continue;
+ }
+ while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
+ ancestor = ancestor.getParent();
+ }
+ }
+ return ancestor;
+ }
+
private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
ArrayList<WindowContainer> sortedTargets) {
// Find the layout params of the top-most application window that is part of the
@@ -1806,10 +1875,19 @@
@Retention(RetentionPolicy.SOURCE)
@interface Flag {}
- // Usually "post" change state.
+ /**
+ * "Parent" that is also included in the transition. When populating the parent changes, we
+ * may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
+ */
WindowContainer mEndParent;
- // Parent before change state.
+ /** Actual parent window before change state. */
WindowContainer mStartParent;
+ /**
+ * When the window is reparented during the transition, this is the common ancestor window
+ * of the {@link #mStartParent} and the current parent. This is needed because the
+ * {@link #mStartParent} may have been detached when the transition starts.
+ */
+ WindowContainer mCommonAncestor;
// State tracking
boolean mExistenceChanged = false;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 26ce4ae..e4d39b9 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -533,6 +533,17 @@
mCollectingTransition.collectVisibleChange(wc);
}
+ /**
+ * Records that a particular container has been reparented. This only effects windows that have
+ * already been collected in the transition. This should be called before reparenting because
+ * the old parent may be removed during reparenting, for example:
+ * {@link Task#shouldRemoveSelfOnLastChildRemoval}
+ */
+ void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
+ if (!isCollecting()) return;
+ mCollectingTransition.collectReparentChange(wc, newParent);
+ }
+
/** @see Transition#mStatusBarTransitionDelay */
void setStatusBarTransitionDelay(long delay) {
if (mCollectingTransition == null) return;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bece476..65c497c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -542,6 +542,10 @@
throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
}
+ // Collect before removing child from old parent, because the old parent may be removed if
+ // this is the last child in it.
+ mTransitionController.collectReparentChange(this, newParent);
+
// The display object before reparenting as that might lead to old parent getting removed
// from the display if it no longer has any child.
final DisplayContent prevDc = oldParent.getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ac720be..9c9d751 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8684,11 +8684,12 @@
h.ownerPid = callingPid;
if (region == null) {
- h.replaceTouchableRegionWithCrop = true;
+ h.replaceTouchableRegionWithCrop(null);
} else {
h.touchableRegion.set(region);
+ h.replaceTouchableRegionWithCrop = false;
+ h.setTouchableRegionCrop(surface);
}
- h.setTouchableRegionCrop(null /* use the input surface's bounds */);
final SurfaceControl.Transaction t = mTransactionFactory.get();
t.setInputWindowInfo(surface, h);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 744bf0a..d4c1abf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1842,8 +1842,8 @@
* @return {@code true} if one or more windows have been displayed, else false.
*/
boolean hasAppShownWindows() {
- return mActivityRecord != null
- && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+ return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+ || mActivityRecord.isStartingWindowDisplayed());
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5c0557f..a0ba8fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- final ActivityRecord activity = mWin.mActivityRecord;
- if (activity != null) {
- if (mWin == activity.mStartingWindow) {
- activity.startingDisplayed = false;
- }
- }
-
if (mSurfaceController == null) {
return;
}
@@ -602,11 +595,17 @@
return true;
}
- final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
- if (isEntrance && isImeWindow) {
+ if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
mWin.getDisplayContent().adjustForImeIfNeeded();
- mWin.setDisplayLayoutNeeded();
- mService.mWindowPlacerLocked.requestTraversal();
+ if (isEntrance) {
+ mWin.setDisplayLayoutNeeded();
+ mService.mWindowPlacerLocked.requestTraversal();
+ }
+ }
+
+ if (mWin.mControllableInsetProvider != null) {
+ // All our animations should be driven by the insets control target.
+ return false;
}
// Only apply an animation if the display isn't frozen. If it is
@@ -654,14 +653,10 @@
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
- } else if (!isImeWindow) {
+ } else {
mWin.cancelAnimation();
}
- if (!isEntrance && isImeWindow) {
- mWin.getDisplayContent().adjustForImeIfNeeded();
- }
-
return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 7610b7c..b33e22f 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -164,7 +164,7 @@
}
public void testRequestPinAppWidget() {
- ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
// Set up users.
when(mMockShortcutService.requestPinAppWidget(anyString(),
any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt()))
@@ -289,6 +289,16 @@
assertEquals(4, updates.size());
}
+ public void testReceiveBroadcastBehavior_enableAndUpdate() {
+ TestAppWidgetProvider testAppWidgetProvider = new TestAppWidgetProvider();
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
+
+ testAppWidgetProvider.onReceive(mTestContext, intent);
+
+ assertTrue(testAppWidgetProvider.isBehaviorSuccess());
+ }
+
+
public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
int widgetId = setupHostAndWidget();
int widgetId2 = bindNewWidget();
@@ -385,7 +395,7 @@
}
private int bindNewWidget() {
- ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java b/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java
deleted file mode 100644
index fd99b21..0000000
--- a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.appwidget;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Placeholder widget for testing
- */
-public class DummyAppWidget extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java
new file mode 100644
index 0000000..6c11a68
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appwidget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+
+/**
+ * Placeholder widget for testing
+ */
+public class TestAppWidgetProvider extends AppWidgetProvider {
+ private boolean mEnabled;
+ private boolean mUpdated;
+
+ TestAppWidgetProvider() {
+ super();
+ mEnabled = false;
+ mUpdated = false;
+ }
+
+ public boolean isBehaviorSuccess() {
+ return mEnabled && mUpdated;
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager,
+ int[] appWidgetids) {
+ mUpdated = true;
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ mEnabled = true;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 606f486..cd4af0a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -616,6 +616,20 @@
verify(mCallback).onClientFinished(any(), eq(true));
}
+ @Test
+ public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ client.onPowerPressed();
+ mLooper.dispatchAll();
+
+ verify(mCallback, never()).onClientFinished(any(), eq(true));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 462957a..8a0a4f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2886,6 +2886,7 @@
fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
task.addChild(taskFragment1, POSITION_TOP);
assertEquals(task, activity1.mStartingData.mAssociatedTask);
+ assertEquals(activity1.mStartingData, task.mSharedStartingData);
final TaskFragment taskFragment2 = new TaskFragment(
mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2905,7 +2906,6 @@
verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
eq(task.mSurfaceControl));
- assertEquals(activity1.mStartingData, startingWindow.mStartingData);
assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
assertEquals(taskFragment1.getBounds(), activity1.getBounds());
// The activity was resized by task fragment, but starting window must still cover the task.
@@ -2916,6 +2916,7 @@
activity1.onFirstWindowDrawn(activityWindow);
activity2.onFirstWindowDrawn(activityWindow);
assertNull(activity1.mStartingWindow);
+ assertNull(task.mSharedStartingData);
}
@Test
@@ -2991,10 +2992,10 @@
final WindowManager.LayoutParams attrs =
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
final TestWindowState startingWindow = createWindowState(attrs, activity);
- activity.startingDisplayed = true;
+ activity.mStartingData = mock(StartingData.class);
activity.addWindow(startingWindow);
assertTrue("Starting window should be present", activity.hasStartingWindow());
- activity.startingDisplayed = false;
+ activity.mStartingData = null;
assertTrue("Starting window should be present", activity.hasStartingWindow());
activity.removeChild(startingWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d..0332c4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@
activity.allDrawn = true;
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
+ // Assume the activity contains a window.
+ doReturn(true).when(activity).hasChild();
// Make sure activity can create remote animation target.
doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f61effa..d55e53c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -321,7 +321,6 @@
final ActivityRecord activity2 = createActivityRecord(dc2);
activity1.allDrawn = true;
- activity1.startingDisplayed = true;
activity1.startingMoved = true;
// Simulate activity resume / finish flows to prepare app transition & set visibility,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 55a7c1b..befe4e8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -24,7 +24,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -139,34 +138,12 @@
}
@Test
- public void testDimAboveNoChildCreatesSurface() {
- final float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- SurfaceControl dimLayer = getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mTransaction).setAlpha(dimLayer, alpha);
- verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
- }
-
- @Test
- public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() {
- float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
- final SurfaceControl firstSurface = getDimLayer();
-
- alpha = 0.9f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- assertEquals(firstSurface, getDimLayer());
- verify(mTransaction).setAlpha(firstSurface, 0.9f);
- }
-
- @Test
public void testUpdateDimsAppliesCrop() {
- mDimmer.dimAbove(mTransaction, 0.8f);
+ TestWindowContainer child = new TestWindowContainer(mWm);
+ mHost.addChild(child, 0);
+
+ final float alpha = 0.8f;
+ mDimmer.dimAbove(mTransaction, child, alpha);
int width = 100;
int height = 300;
@@ -178,17 +155,6 @@
}
@Test
- public void testDimAboveNoChildNotReset() {
- mDimmer.dimAbove(mTransaction, 0.8f);
- SurfaceControl dimLayer = getDimLayer();
- mDimmer.resetDimStates();
-
- mDimmer.updateDims(mTransaction, new Rect());
- verify(mTransaction).show(getDimLayer());
- verify(mTransaction, never()).remove(dimLayer);
- }
-
- @Test
public void testDimAboveWithChildCreatesSurfaceAboveChild() {
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 66e46a2..54bcbd9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1517,6 +1517,29 @@
transition.abort();
}
+ @Test
+ public void testCollectReparentChange() {
+ registerTestTransitionPlayer();
+
+ // Reparent activity in transition.
+ final Task lastParent = createTask(mDisplayContent);
+ final Task newParent = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(lastParent);
+ doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
+ doNothing().when(activity).setDropInputMode(anyInt());
+ activity.mVisibleRequested = true;
+
+ final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
+ activity.mTransitionController, mWm.mSyncEngine);
+ activity.mTransitionController.moveToCollecting(transition);
+ transition.collect(activity);
+ activity.reparent(newParent, POSITION_TOP);
+
+ // ChangeInfo#mCommonAncestor should be set after reparent.
+ final Transition.ChangeInfo change = transition.mChanges.get(activity);
+ assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 0693c59..79fc0e8 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4023,6 +4023,10 @@
* cautiously, for example, after formatting the number to a consistent format with
* {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
*
+ * <p>The availability and correctness of the phone number depends on the underlying source
+ * and the network etc. Additional verification is needed to use this number for
+ * security-related or other sensitive scenarios.
+ *
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
* @return the phone number, or an empty string if not available.