Merge "Revert "Send update config change when letterbox is moved"" into udc-qpr-dev
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d8cedb8..7ee1332 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -26,12 +26,14 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.media.AudioAttributes;
+import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -54,6 +56,7 @@
* A representation of settings that apply to a collection of similarly themed notifications.
*/
public final class NotificationChannel implements Parcelable {
+ private static final String TAG = "NotificationChannel";
/**
* The id of the default channel for an app. This id is reserved by the system. All
@@ -959,8 +962,11 @@
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
Uri sound = safeUri(parser, ATT_SOUND);
- setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
- safeAudioAttributes(parser));
+
+ final AudioAttributes audioAttributes = safeAudioAttributes(parser);
+ final int usage = audioAttributes.getUsage();
+ setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled, usage) : sound,
+ audioAttributes);
enableLights(safeBool(parser, ATT_LIGHTS, false));
setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -1010,18 +1016,34 @@
if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri;
}
-
return contentResolver.canonicalize(uri);
}
@Nullable
- private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+ private Uri getUncanonicalizedSoundUri(
+ ContentResolver contentResolver, @NonNull Uri uri, int usage) {
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
|| ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
|| ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri;
}
- return contentResolver.uncanonicalize(uri);
+ int ringtoneType = 0;
+
+ // Consistent with UI(SoundPreferenceController.handlePreferenceTreeClick).
+ if (AudioAttributes.USAGE_ALARM == usage) {
+ ringtoneType = RingtoneManager.TYPE_ALARM;
+ } else if (AudioAttributes.USAGE_NOTIFICATION_RINGTONE == usage) {
+ ringtoneType = RingtoneManager.TYPE_RINGTONE;
+ } else {
+ ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
+ }
+ try {
+ return RingtoneManager.getRingtoneUriForRestore(
+ contentResolver, uri.toString(), ringtoneType);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to uncanonicalized sound uri for " + uri + " " + e);
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ }
}
/**
@@ -1033,7 +1055,8 @@
* @hide
*/
@Nullable
- public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
+ public Uri restoreSoundUri(
+ Context context, @Nullable Uri uri, boolean pkgInstalled, int usage) {
if (uri == null || Uri.EMPTY.equals(uri)) {
return null;
}
@@ -1060,7 +1083,7 @@
}
}
mSoundRestored = true;
- return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
+ return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri, usage);
}
/**
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 3ffbe1d..2ea6513 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -101,20 +101,6 @@
],
"presubmit-large":[
{
- "name":"CtsContentTestCases",
- "options":[
- {
- "exclude-annotation":"androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation":"org.junit.Ignore"
- },
- {
- "include-filter":"android.content.pm.cts"
- }
- ]
- },
- {
"name":"CtsUsesNativeLibraryTest",
"options":[
{
@@ -156,6 +142,20 @@
],
"postsubmit":[
{
+ "name":"CtsContentTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ },
+ {
+ "include-filter":"android.content.pm.cts"
+ }
+ ]
+ },
+ {
"name":"CtsAppSecurityHostTestCases",
"options":[
{
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index fc3dc79..946b5f3 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -261,7 +261,10 @@
/**
* @param type the type of the credential to be stored
- * @param credentialData the full credential creation request data
+ * @param credentialData the full credential creation request data, which must at minimum
+ * contain the required fields observed at the
+ * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods,
+ * because they are required for properly displaying the system credential selector UI
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial creation candidate query stage
*/
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 65677cd..c576286 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -913,7 +913,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
@@ -983,7 +982,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Make this public API.
String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
"android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
@@ -1018,7 +1016,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
@@ -1056,7 +1053,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
@@ -1102,7 +1098,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
@@ -1151,7 +1146,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
@@ -1189,7 +1183,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
@@ -1233,7 +1226,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
@@ -1300,6 +1292,102 @@
"android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
/**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * user-facing aspect ratio compatibility override.
+ *
+ * <p>The compatibility override enables device users to set the app's aspect
+ * ratio or force the app to fill the display regardless of the aspect
+ * ratio or orientation specified in the app manifest.
+ *
+ * <p>The aspect ratio compatibility override is exposed to users in device
+ * settings. A menu in device settings lists all apps that don't opt out of
+ * the compatibility override. Users select apps from the menu and set the
+ * app aspect ratio on a per-app basis. Typically, the menu is available
+ * only on large screen devices.
+ *
+ * <p>When users apply the aspect ratio override, the minimum aspect ratio
+ * specified in the app manifest is overridden. If users choose a
+ * full-screen aspect ratio, the orientation of the activity is forced to
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER};
+ * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
+ * disable the full-screen option only.
+ *
+ * <p>The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+ * Large screen app compatibility</a>
+ * for more details).
+ *
+ * <p>To opt out of the user aspect ratio compatibility override, add this property
+ * to your app manifest and set the value to {@code false}. Your app will be excluded
+ * from the list of apps in device settings, and users will not be able to override
+ * the app's aspect ratio.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
+
+ /**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * full-screen option of the aspect ratio compatibility override. (For
+ * background information about the aspect ratio compatibility override, see
+ * {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
+ *
+ * <p>When users apply the aspect ratio compatibility override, the orientation
+ * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
+ *
+ * <p>The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+ * Large screen app compatibility</a>
+ * for more details).
+ *
+ * <p>To opt out of the full-screen option of the user aspect ratio compatibility
+ * override, add this property to your app manifest and set the value to {@code false}.
+ * Your app will have full-screen option removed from the list of user aspect ratio
+ * override options in device settings, and users will not be able to apply
+ * full-screen override to your app.
+ *
+ * <p><b>Note:</b> If {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE} is
+ * {@code false}, this property has no effect.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2ec3818..fea3b78 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -4820,7 +4820,7 @@
public static boolean isAdapterConversionEnabled() {
return AppGlobals.getIntCoreSetting(
SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
- SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1;
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
}
/**
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 0ea8014..4261a0f 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -234,6 +234,23 @@
}
/**
+ * Allows subscription to {@link android.service.voice.VisualQueryDetectionService} service
+ * status.
+ *
+ * @param listener to receive visual service start/stop events.
+ */
+ public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+ listener) {
+ try {
+ if (mVoiceInteractionManagerService != null) {
+ mVoiceInteractionManagerService.subscribeVisualQueryRecognitionStatus(listener);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register visual query detection start listener", e);
+ }
+ }
+
+ /**
* Enables visual detection service.
*
* @param listener to receive visual attention gained/lost events.
diff --git a/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
new file mode 100644
index 0000000..cc49a75
--- /dev/null
+++ b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package com.android.internal.app;
+
+
+ oneway interface IVisualQueryRecognitionStatusListener {
+ /**
+ * Called when {@link VisualQueryDetectionService#onStartDetection} is scheduled from the system
+ * server via {@link VoiceInteractionManagerService#StartPerceiving}.
+ */
+ void onStartPerceiving();
+
+ /**
+ * Called when {@link VisualQueryDetectionService#onStopDetection} is scheduled from the system
+ * server via {@link VoiceInteractionManagerService#StopPerceiving}.
+ */
+ void onStopPerceiving();
+ }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 24d5afc..314ed69 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -40,6 +40,7 @@
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
interface IVoiceInteractionManagerService {
void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -325,6 +326,9 @@
void shutdownHotwordDetectionService();
@EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+ void subscribeVisualQueryRecognitionStatus(in IVisualQueryRecognitionStatusListener listener);
+
+ @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
@EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 43d263b..b3b0603 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -390,12 +390,17 @@
public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
List<InputMethodSubtype> list) {
Set<LocaleInfo> imeLocales = new HashSet<>();
+ Set<String> languageTagSet = new HashSet<>();
for (InputMethodSubtype subtype : list) {
- Locale locale = Locale.forLanguageTag(subtype.getLanguageTag());
- LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
- LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
- localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
- imeLocales.add(localeInfo);
+ String languageTag = subtype.getLanguageTag();
+ if (!languageTagSet.contains(languageTag)) {
+ languageTagSet.add(languageTag);
+ Locale locale = Locale.forLanguageTag(languageTag);
+ LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
+ LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
+ localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
+ imeLocales.add(localeInfo);
+ }
}
return imeLocales;
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 0ba271f..3aa554a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -534,7 +534,14 @@
/**
* (boolean) Whether to enable the adapter conversion in RemoteViews
*/
- public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+ public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
+ "CursorControlFeature__remoteviews_adapter_conversion";
+
+ /**
+ * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
+ */
+ public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
+ "systemui__remoteviews_adapter_conversion";
/**
* Default value for whether the adapter conversion is enabled or not. This is set for
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 635adca..7dda91d 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -383,7 +383,11 @@
updateContentEndPaddings();
}
- @RemotableViewMethod
+ /**
+ * Set conversation data
+ * @param extras Bundle contains conversation data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
@@ -393,8 +397,7 @@
= Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
// mUser now set (would be nice to avoid the side effect but WHATEVER)
- setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, android.app.Person.class));
-
+ final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
@@ -402,11 +405,30 @@
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- // bind it, baby
- bind(newMessages, newHistoricMessages, showSpinner);
-
int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
- setUnreadCount(unreadCount);
+
+ // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
+ // if they exist
+ final List<MessagingMessage> newMessagingMessages =
+ createMessages(newMessages, false /* isHistoric */);
+ final List<MessagingMessage> newHistoricMessagingMessages =
+ createMessages(newHistoricMessages, true /* isHistoric */);
+ // bind it, baby
+ bindViews(user, showSpinner, unreadCount,
+ newMessagingMessages,
+ newHistoricMessagingMessages);
+ }
+
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains conversation data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
}
@Override
@@ -436,15 +458,17 @@
}
}
- private void bind(List<Notification.MessagingStyle.Message> newMessages,
- List<Notification.MessagingStyle.Message> newHistoricMessages,
- boolean showSpinner) {
- // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
- // if they exist
- List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
- true /* isHistoric */);
- List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+ private void bindViews(Person user,
+ boolean showSpinner, int unreadCount, List<MessagingMessage> newMessagingMessages,
+ List<MessagingMessage> newHistoricMessagingMessages) {
+ setUser(user);
+ setUnreadCount(unreadCount);
+ bind(showSpinner, newMessagingMessages, newHistoricMessagingMessages);
+ }
+
+ private void bind(boolean showSpinner, List<MessagingMessage> messages,
+ List<MessagingMessage> historicMessages) {
// Copy our groups, before they get clobbered
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9d142f6..8345c5c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -156,7 +156,11 @@
mConversationTitle = conversationTitle;
}
- @RemotableViewMethod
+ /**
+ * Set Messaging data
+ * @param extras Bundle contains messaging data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
@@ -168,9 +172,28 @@
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
addRemoteInputHistoryToMessages(newMessages, history);
+
+ final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- bind(newMessages, newHistoricMessages, showSpinner);
+
+ final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
+ true /* isHistoric */);
+ final List<MessagingMessage> newMessagingMessages =
+ createMessages(newMessages, false /* isHistoric */);
+ bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
+ }
+
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains messaging data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
}
@Override
@@ -195,14 +218,15 @@
}
}
- private void bind(List<Notification.MessagingStyle.Message> newMessages,
- List<Notification.MessagingStyle.Message> newHistoricMessages,
- boolean showSpinner) {
+ private void bindViews(Person user, boolean showSpinner,
+ List<MessagingMessage> historicMessagingMessages,
+ List<MessagingMessage> newMessagingMessages) {
+ setUser(user);
+ bind(showSpinner, historicMessagingMessages, newMessagingMessages);
+ }
- List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
- true /* isHistoric */);
- List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
-
+ private void bind(boolean showSpinner, List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages) {
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
addMessagesToGroups(historicMessages, messages, showSpinner);
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index a95b6e3..76f5c10 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -127,16 +127,17 @@
}
}
-static void throwReadRE(JNIEnv *env, binder_status_t status) {
+static void throwReadException(JNIEnv *env, binder_status_t status) {
ALOGE("Could not read LongArrayMultiStateCounter from Parcel, status = %d", status);
- jniThrowRuntimeException(env, "Could not read LongArrayMultiStateCounter from Parcel");
+ jniThrowException(env, "android.os.BadParcelableException",
+ "Could not read LongArrayMultiStateCounter from Parcel");
}
#define THROW_AND_RETURN_ON_READ_ERROR(expr) \
{ \
binder_status_t status = expr; \
if (status != STATUS_OK) { \
- throwReadRE(env, status); \
+ throwReadException(env, status); \
return 0L; \
} \
}
@@ -147,6 +148,11 @@
int32_t stateCount;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
+ if (stateCount < 0 || stateCount > 0xEFFF) {
+ throwReadException(env, STATUS_INVALID_OPERATION);
+ return 0L;
+ }
+
int32_t arrayLength;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 1712b3a8..ddf7a67 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -131,16 +131,17 @@
}
}
-static void throwReadRE(JNIEnv *env, binder_status_t status) {
+static void throwReadException(JNIEnv *env, binder_status_t status) {
ALOGE("Could not read LongMultiStateCounter from Parcel, status = %d", status);
- jniThrowRuntimeException(env, "Could not read LongMultiStateCounter from Parcel");
+ jniThrowException(env, "android.os.BadParcelableException",
+ "Could not read LongMultiStateCounter from Parcel");
}
#define THROW_AND_RETURN_ON_READ_ERROR(expr) \
{ \
binder_status_t status = expr; \
if (status != STATUS_OK) { \
- throwReadRE(env, status); \
+ throwReadException(env, status); \
return 0L; \
} \
}
@@ -151,6 +152,11 @@
int32_t stateCount;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
+ if (stateCount < 0 || stateCount > 0xEFFF) {
+ throwReadException(env, STATUS_INVALID_OPERATION);
+ return 0L;
+ }
+
auto counter = std::make_unique<battery::LongMultiStateCounter>(stateCount, 0);
for (battery::state_t state = 0; state < stateCount; state++) {
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 647bfe8..d8305f0 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -16,19 +16,52 @@
package android.app;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.AttributionSource;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.database.MatrixCursor;
+import android.media.AudioAttributes;
import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.Parcel;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.test.mock.MockContentResolver;
+import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import com.google.common.base.Strings;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
@RunWith(AndroidJUnit4.class)
@@ -36,6 +69,88 @@
public class NotificationChannelTest {
private final String CLASS = "android.app.NotificationChannel";
+ Context mContext;
+ ContentProvider mContentProvider;
+ IContentProvider mIContentProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = mock(Context.class);
+ when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ MockContentResolver mContentResolver = new MockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ mContentProvider = mock(ContentProvider.class);
+ mIContentProvider = mock(IContentProvider.class);
+ when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider);
+ doAnswer(
+ invocation -> {
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putParcelable(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.canonicalize(attributionSource, uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .canonicalizeAsync(any(), any(), any());
+ doAnswer(
+ invocation -> {
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putParcelable(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.uncanonicalize(attributionSource, uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .uncanonicalizeAsync(any(), any(), any());
+ doAnswer(
+ invocation -> {
+ Uri uri = invocation.getArgument(0);
+ RemoteCallback cb = invocation.getArgument(1);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putString(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.getType(uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .getTypeAsync(any(), any());
+
+ mContentResolver.addProvider("media", mContentProvider);
+ }
+
@Test
public void testLongStringFields() {
NotificationChannel channel = new NotificationChannel("id", "name", 3);
@@ -103,4 +218,139 @@
assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
fromParcel.getSound().toString().length());
}
+
+ @Test
+ public void testRestoreSoundUri_customLookup() throws Exception {
+ Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+ Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+ Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+ Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+ cursor.addRow(new Object[] {100L});
+
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ // Mock the failure of regular uncanonicalize.
+ when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(null);
+
+ // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+ .thenReturn(uriAfterRestoredCanonicalized);
+
+ assertThat(
+ channel.restoreSoundUri(
+ mContext,
+ uriToBeRestoredUncanonicalized,
+ true,
+ AudioAttributes.USAGE_NOTIFICATION))
+ .isEqualTo(uriAfterRestoredCanonicalized);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_notificationUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_NOTIFICATION, AudioColumns.IS_NOTIFICATION);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_alarmUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(AudioAttributes.USAGE_ALARM, AudioColumns.IS_ALARM);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_ringtoneUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_NOTIFICATION_RINGTONE, AudioColumns.IS_RINGTONE);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_unknownUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_UNKNOWN, AudioColumns.IS_NOTIFICATION);
+ }
+
+ private void testWriteXmlForBackup_customLookup(int usage, String customQuerySelection)
+ throws Exception {
+ Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+ Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+ Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+ Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+ AudioAttributes mAudioAttributes =
+ new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(usage)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
+
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+
+ // mock the canonicalize in writeXmlForBackup -> getSoundForBackup
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ channel.writeXmlForBackup(serializer, mContext);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ byte[] byteArray = baos.toByteArray();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+ parser.nextTag();
+
+ NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+ cursor.addRow(new Object[] {100L});
+
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ // Mock the failure of regular uncanonicalize.
+ when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(null);
+
+ Bundle expectedBundle =
+ ContentResolver.createSqlQueryBundle(
+ customQuerySelection + "=1 AND title=?", new String[] {"Song"}, null);
+
+ // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.query(
+ any(),
+ any(),
+ any(),
+ // any(),
+ argThat(
+ queryBundle -> {
+ return queryBundle != null
+ && expectedBundle
+ .toString()
+ .equals(queryBundle.toString());
+ }),
+ any()))
+ .thenReturn(cursor);
+
+ // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+ .thenReturn(uriAfterRestoredCanonicalized);
+
+ targetChannel.populateFromXmlForRestore(parser, true, mContext);
+ assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index 516dee7..faccf1a 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertThrows;
+import android.os.BadParcelableException;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -163,6 +164,45 @@
}
@Test
+ public void createFromBadBundle() {
+ Parcel data = Parcel.obtain();
+ int bundleLenPos = data.dataPosition();
+ data.writeInt(0);
+ data.writeInt(0x4C444E42); // BaseBundle.BUNDLE_MAGIC
+
+ int bundleStart = data.dataPosition();
+
+ data.writeInt(1);
+ data.writeString("key");
+ data.writeInt(4);
+ int lazyValueLenPos = data.dataPosition();
+ data.writeInt(0);
+ int lazyValueStart = data.dataPosition();
+ data.writeString("com.android.internal.os.LongArrayMultiStateCounter");
+
+ // Invalid int16 value
+ data.writeInt(0x10000); // stateCount
+ data.writeInt(10); // arrayLength
+ for (int i = 0; i < 0x10000; ++i) {
+ data.writeLong(0);
+ }
+
+ backPatchLength(data, lazyValueLenPos, lazyValueStart);
+ backPatchLength(data, bundleLenPos, bundleStart);
+ data.setDataPosition(0);
+
+ assertThrows(BadParcelableException.class,
+ () -> data.readBundle().getParcelable("key", LongArrayMultiStateCounter.class));
+ }
+
+ private static void backPatchLength(Parcel parcel, int lengthPos, int startPos) {
+ int endPos = parcel.dataPosition();
+ parcel.setDataPosition(lengthPos);
+ parcel.writeInt(endPos - startPos);
+ parcel.setDataPosition(endPos);
+ }
+
+ @Test
public void combineValues() {
long[] values = new long[] {0, 1, 2, 3, 42};
LongArrayMultiStateCounter.LongArrayContainer container =
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index fc86ebe..3413753 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertThrows;
+import android.os.BadParcelableException;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -210,4 +211,42 @@
assertThrows(RuntimeException.class,
() -> LongMultiStateCounter.CREATOR.createFromParcel(parcel));
}
+
+ @Test
+ public void createFromBadBundle() {
+ Parcel data = Parcel.obtain();
+ int bundleLenPos = data.dataPosition();
+ data.writeInt(0);
+ data.writeInt(0x4C444E42); // BaseBundle.BUNDLE_MAGIC
+
+ int bundleStart = data.dataPosition();
+
+ data.writeInt(1);
+ data.writeString("key");
+ data.writeInt(4);
+ int lazyValueLenPos = data.dataPosition();
+ data.writeInt(0);
+ int lazyValueStart = data.dataPosition();
+ data.writeString("com.android.internal.os.LongMultiStateCounter");
+
+ // Invalid int16 value
+ data.writeInt(0x10000); // stateCount
+ for (int i = 0; i < 0x10000; ++i) {
+ data.writeLong(0);
+ }
+
+ backPatchLength(data, lazyValueLenPos, lazyValueStart);
+ backPatchLength(data, bundleLenPos, bundleStart);
+ data.setDataPosition(0);
+
+ assertThrows(BadParcelableException.class,
+ () -> data.readBundle().getParcelable("key", LongMultiStateCounter.class));
+ }
+
+ private static void backPatchLength(Parcel parcel, int lengthPos, int startPos) {
+ int endPos = parcel.dataPosition();
+ parcel.setDataPosition(lengthPos);
+ parcel.writeInt(endPos - startPos);
+ parcel.setDataPosition(endPos);
+ }
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 94e23e7..71e9263 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -427,12 +427,6 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
- "-1715268616": {
- "message": "Last window, removing starting window %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1710206702": {
"message": "Display id=%d is frozen while keyguard locked, return %d",
"level": "VERBOSE",
@@ -691,6 +685,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-1449515133": {
+ "message": "Content Recording: stopping active projection for display %d",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1443029505": {
"message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
"level": "INFO",
@@ -1279,6 +1279,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-921346089": {
+ "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection for display %d: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-917215012": {
"message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
"level": "WARN",
@@ -2227,12 +2233,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-88873335": {
- "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection: %s",
- "level": "ERROR",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-87705714": {
"message": "findFocusedWindow: focusedApp=null using new focus @ %s",
"level": "VERBOSE",
@@ -4057,12 +4057,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "1671994402": {
- "message": "Nulling last startingData",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1674747211": {
"message": "%s forcing orientation to %d for display id=%d",
"level": "VERBOSE",
@@ -4243,12 +4237,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1853793312": {
- "message": "Notify removed startingWindow %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1856783490": {
"message": "resumeTopActivity: Restarting %s",
"level": "DEBUG",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index e95e8e5..1b41f79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -41,9 +41,9 @@
private val ANIMATE_DURATION: Long = 200
private val positioner: BubblePositioner = positioner
- private val manageView by lazy { findViewById<ViewGroup>(R.id.manage_education_view) }
- private val manageButton by lazy { findViewById<Button>(R.id.manage_button) }
- private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
+ private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
+ private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
+ private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
private var isHiding = false
private var realManageButtonRect = Rect()
@@ -122,7 +122,7 @@
manageButton
.setOnClickListener {
hide()
- expandedView.findViewById<View>(R.id.manage_button).performClick()
+ expandedView.requireViewById<View>(R.id.manage_button).performClick()
}
gotItButton.setOnClickListener { hide() }
setOnClickListener { hide() }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index d0598cd..5e3a077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -48,9 +48,9 @@
private val positioner: BubblePositioner = positioner
private val controller: BubbleController = controller
- private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
- private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
- private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
+ private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
+ private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
+ private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
var isHiding = false
private set
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
similarity index 98%
rename from core/java/com/android/internal/policy/DividerSnapAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index a065e2b..1901e0b 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy;
+package com.android.wm.shell.common.split;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 2dbc444..0b0c693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -53,7 +53,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
similarity index 97%
rename from core/java/com/android/internal/policy/DockedDividerUtils.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
index b61b9de..f25dfea 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy;
+package com.android.wm.shell.common.split;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
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 d3fada3..5d7e532 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
@@ -25,8 +25,8 @@
import static android.view.WindowManager.DOCKED_TOP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -58,8 +58,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index a4c086b..d8ce427 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -313,7 +313,7 @@
task.taskId
)
val wct = WindowContainerTransaction()
- wct.setBounds(task.token, null)
+ wct.setBounds(task.token, Rect())
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
@@ -550,6 +550,7 @@
)
// Check if we should skip handling this transition
var reason = ""
+ val triggerTask = request.triggerTask
val shouldHandleRequest =
when {
// Only handle open or to front transitions
@@ -558,19 +559,19 @@
false
}
// Only handle when it is a task transition
- request.triggerTask == null -> {
+ triggerTask == null -> {
reason = "triggerTask is null"
false
}
// Only handle standard type tasks
- request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
- reason = "activityType not handled (${request.triggerTask.activityType})"
+ triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
+ reason = "activityType not handled (${triggerTask.activityType})"
false
}
// Only handle fullscreen or freeform tasks
- request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
- request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
- reason = "windowingMode not handled (${request.triggerTask.windowingMode})"
+ triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+ triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+ reason = "windowingMode not handled (${triggerTask.windowingMode})"
false
}
// Otherwise process it
@@ -586,17 +587,17 @@
return null
}
- val task: RunningTaskInfo = request.triggerTask
-
- val result = when {
- // If display has tasks stashed, handle as stashed launch
- desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
- // Check if fullscreen task should be updated
- task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
- // Check if freeform task should be updated
- task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
- else -> {
- null
+ val result = triggerTask?.let { task ->
+ when {
+ // If display has tasks stashed, handle as stashed launch
+ desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+ // Check if fullscreen task should be updated
+ task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+ // Check if freeform task should be updated
+ task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+ else -> {
+ null
+ }
}
}
KtProtoLog.v(
@@ -703,7 +704,7 @@
WINDOWING_MODE_FULLSCREEN
}
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
- wct.setBounds(taskInfo.token, null)
+ wct.setBounds(taskInfo.token, Rect())
if (isDesktopDensityOverrideSet()) {
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index b9cb5c7..9debb25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -69,7 +69,7 @@
): Boolean {
val change = findRelevantChange(info)
val leash = change.leash
- val taskId = change.taskInfo.taskId
+ val taskId = checkNotNull(change.taskInfo).taskId
val startBounds = change.startAbsBounds
val endBounds = change.endAbsBounds
val windowDecor =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 672e57a..a9eb882 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -23,14 +23,14 @@
appIcon: Drawable
) : DesktopModeWindowDecorationViewHolder(rootView) {
- private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
- private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
- private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
- private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
- private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
- private val maximizeWindowButton: ImageButton = rootView.findViewById(R.id.maximize_window)
- private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
- private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+ private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+ private val captionHandle: View = rootView.requireViewById(R.id.caption_handle)
+ private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
+ private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
+ private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+ private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
+ private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
+ private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
init {
captionView.setOnTouchListener(onCaptionTouchListener)
@@ -47,7 +47,9 @@
override fun bindData(taskInfo: RunningTaskInfo) {
val captionDrawable = captionView.background as GradientDrawable
- captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+ taskInfo.taskDescription?.statusBarColor?.let {
+ captionDrawable.setColor(it)
+ }
closeWindowButton.imageTintList = ColorStateList.valueOf(
getCaptionCloseButtonColor(taskInfo))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 47a12a0..9374ac9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -17,8 +17,8 @@
onCaptionButtonClickListener: View.OnClickListener
) : DesktopModeWindowDecorationViewHolder(rootView) {
- private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
- private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+ private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+ private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
init {
captionView.setOnTouchListener(onCaptionTouchListener)
@@ -27,9 +27,10 @@
}
override fun bindData(taskInfo: RunningTaskInfo) {
- val captionColor = taskInfo.taskDescription.statusBarColor
- val captionDrawable = captionView.background as GradientDrawable
- captionDrawable.setColor(captionColor)
+ taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
+ val captionDrawable = captionView.background as GradientDrawable
+ captionDrawable.setColor(captionColor)
+ }
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index d293cf7..49e8d15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -25,11 +25,14 @@
* with the caption background color.
*/
protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
- return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0 &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
- } else {
- taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
- }
+ return taskInfo.taskDescription
+ ?.let { taskDescription ->
+ if (Color.alpha(taskDescription.statusBarColor) != 0 &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
+ } else {
+ taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+ }
+ } ?: false
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index ad4d97f..c2f184a 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -43,6 +43,7 @@
"frameworks-base-testutils",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
+ "mockito-kotlin2",
"mockito-target-extended-minus-junit4",
"truth-prebuilt",
"testables",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index 0e05e01..e359957 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -29,11 +29,11 @@
import org.junit.After
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
class BubbleDataRepositoryTest : ShellTestCase() {
@@ -124,7 +124,7 @@
private val testHandler = Handler(Looper.getMainLooper())
private val mainExecutor = HandlerExecutor(testHandler)
- private val launcherApps = mock(LauncherApps::class.java)
+ private val launcherApps = mock<LauncherApps>()
private val persistedBubbles = SparseArray<List<BubbleEntity>>()
@@ -158,8 +158,7 @@
assertThat(persistedBubbles).isEqualTo(validEntitiesByUser)
// No invalid users, so no persist to disk happened
- verify(dataRepository, never()).persistToDisk(
- any(SparseArray<List<BubbleEntity>>()::class.java))
+ verify(dataRepository, never()).persistToDisk(any())
}
@Test
@@ -199,6 +198,4 @@
// Verify that persist to disk happened with the new valid entities list.
verify(dataRepository).persistToDisk(validEntitiesByUser)
}
-
- fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 443cea2..fe2da5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -38,7 +38,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index d2b21ae..43acdd5 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -46,7 +46,9 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.BaseColumns;
import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
import android.provider.MediaStore.MediaColumns;
import android.provider.Settings;
import android.provider.Settings.System;
@@ -507,6 +509,95 @@
return getUriFromCursor(mContext, mCursor);
}
+ /**
+ * Gets the valid ringtone uri by a given uri string and ringtone type for the restore purpose.
+ *
+ * @param contentResolver ContentResolver to execute media query.
+ * @param value a canonicalized uri which refers to the ringtone.
+ * @param ringtoneType an integer representation of the kind of uri that is being restored, can
+ * be RingtoneManager.TYPE_RINGTONE, RingtoneManager.TYPE_NOTIFICATION, or
+ * RingtoneManager.TYPE_ALARM.
+ * @hide
+ */
+ public static @Nullable Uri getRingtoneUriForRestore(
+ @NonNull ContentResolver contentResolver, @Nullable String value, int ringtoneType)
+ throws FileNotFoundException, IllegalArgumentException {
+ if (value == null) {
+ // Return a valid null. It means the null value is intended instead of a failure.
+ return null;
+ }
+
+ Uri ringtoneUri;
+ final Uri canonicalUri = Uri.parse(value);
+
+ // Try to get the media uri via the regular uncanonicalize method first.
+ ringtoneUri = contentResolver.uncanonicalize(canonicalUri);
+ if (ringtoneUri != null) {
+ // Canonicalize it to make the result contain the right metadata of the media asset.
+ ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+ return ringtoneUri;
+ }
+
+ // Query the media by title and ringtone type.
+ final String title = canonicalUri.getQueryParameter(AudioColumns.TITLE);
+ Uri baseUri = ContentUris.removeId(canonicalUri).buildUpon().clearQuery().build();
+ String ringtoneTypeSelection = "";
+ switch (ringtoneType) {
+ case RingtoneManager.TYPE_RINGTONE:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_RINGTONE;
+ break;
+ case RingtoneManager.TYPE_NOTIFICATION:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
+ break;
+ case RingtoneManager.TYPE_ALARM:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_ALARM;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown ringtone type: " + ringtoneType);
+ }
+
+ final String selection = ringtoneTypeSelection + "=1 AND " + AudioColumns.TITLE + "=?";
+ Cursor cursor = null;
+ try {
+ cursor =
+ contentResolver.query(
+ baseUri,
+ /* projection */ new String[] {BaseColumns._ID},
+ /* selection */ selection,
+ /* selectionArgs */ new String[] {title},
+ /* sortOrder */ null,
+ /* cancellationSignal */ null);
+
+ } catch (IllegalArgumentException e) {
+ throw new FileNotFoundException("Volume not found for " + baseUri);
+ }
+ if (cursor == null) {
+ throw new FileNotFoundException("Missing cursor for " + baseUri);
+ } else if (cursor.getCount() == 0) {
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException("No item found for " + baseUri);
+ } else if (cursor.getCount() > 1) {
+ // Find more than 1 result.
+ // We are not sure which one is the right ringtone file so just abandon this case.
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException(
+ "Find multiple ringtone candidates by title+ringtone_type query: count: "
+ + cursor.getCount());
+ }
+ if (cursor.moveToFirst()) {
+ ringtoneUri = ContentUris.withAppendedId(baseUri, cursor.getLong(0));
+ FileUtils.closeQuietly(cursor);
+ } else {
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException("Failed to read row from the result.");
+ }
+
+ // Canonicalize it to make the result contain the right metadata of the media asset.
+ ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+ Log.v(TAG, "Find a valid result: " + ringtoneUri);
+ return ringtoneUri;
+ }
+
private static Uri getUriFromCursor(Context context, Cursor cursor) {
final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)),
cursor.getLong(ID_COLUMN_INDEX));
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index fb72c7b..223b432c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -83,6 +83,7 @@
try {
mImpl.start(new MediaProjectionCallback());
} catch (RemoteException e) {
+ Log.e(TAG, "Content Recording: Failed to start media projection", e);
throw new RuntimeException("Failed to start media projection", e);
}
mDisplayManager = displayManager;
@@ -105,11 +106,18 @@
* @see #unregisterCallback
*/
public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
- final Callback c = Objects.requireNonNull(callback);
- if (handler == null) {
- handler = new Handler();
+ try {
+ final Callback c = Objects.requireNonNull(callback);
+ if (handler == null) {
+ handler = new Handler();
+ }
+ mCallbacks.put(c, new CallbackRecord(c, handler));
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Content Recording: cannot register null Callback", e);
+ throw e;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Content Recording: failed to create new Handler to register Callback", e);
}
- mCallbacks.put(c, new CallbackRecord(c, handler));
}
/**
@@ -120,8 +128,13 @@
* @see #registerCallback
*/
public void unregisterCallback(@NonNull Callback callback) {
- final Callback c = Objects.requireNonNull(callback);
- mCallbacks.remove(c);
+ try {
+ final Callback c = Objects.requireNonNull(callback);
+ mCallbacks.remove(c);
+ } catch (NullPointerException e) {
+ Log.d(TAG, "Content Recording: cannot unregister null Callback", e);
+ throw e;
+ }
}
/**
@@ -203,9 +216,11 @@
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
if (shouldMediaProjectionRequireCallback()) {
if (mCallbacks.isEmpty()) {
- throw new IllegalStateException(
+ final IllegalStateException e = new IllegalStateException(
"Must register a callback before starting capture, to manage resources in"
+ " response to MediaProjection states.");
+ Log.e(TAG, "Content Recording: no callback registered for virtual display", e);
+ throw e;
}
}
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
@@ -272,6 +287,7 @@
*/
public void stop() {
try {
+ Log.d(TAG, "Content Recording: stopping projection");
mImpl.stop();
} catch (RemoteException e) {
Log.e(TAG, "Unable to stop projection", e);
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5a68c53..9790d02 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -256,6 +256,7 @@
*/
public void stopActiveProjection() {
try {
+ Log.d(TAG, "Content Recording: stopping active projection");
mService.stopActiveProjection();
} catch (RemoteException e) {
Log.e(TAG, "Unable to stop the currently active media projection", e);
@@ -269,6 +270,7 @@
*/
public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
if (callback == null) {
+ Log.w(TAG, "Content Recording: cannot add null callback");
throw new IllegalArgumentException("callback must not be null");
}
CallbackDelegate delegate = new CallbackDelegate(callback, handler);
@@ -286,6 +288,7 @@
*/
public void removeCallback(@NonNull Callback callback) {
if (callback == null) {
+ Log.w(TAG, "ContentRecording: cannot remove null callback");
throw new IllegalArgumentException("callback must not be null");
}
CallbackDelegate delegate = mCallbacks.remove(callback);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 0c4cb8e..74acf67 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
@@ -714,8 +715,13 @@
try {
mPrinterForInfoIntent = printer;
+ Bundle options = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle();
startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
- INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+ INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0,
+ options);
} catch (SendIntentException e) {
mPrinterForInfoIntent = null;
Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 1251b0d..9ab84d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -26,6 +26,7 @@
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_PRESENT;
import static android.os.BatteryManager.EXTRA_STATUS;
+import static android.os.OsProtoEnums.BATTERY_PLUGGED_NONE;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +41,8 @@
*/
public class BatteryStatus {
private static final int LOW_BATTERY_THRESHOLD = 20;
+ private static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
+ private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
public static final int CHARGING_UNKNOWN = -1;
@@ -90,21 +93,7 @@
present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
this.incompatibleCharger = incompatibleCharger;
- final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
- -1);
- int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
-
- if (maxChargingMicroVolt <= 0) {
- maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
- }
- if (maxChargingMicroAmp > 0) {
- // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
- // to maintain precision equally on both factors.
- maxChargingWattage = (maxChargingMicroAmp / 1000)
- * (maxChargingMicroVolt / 1000);
- } else {
- maxChargingWattage = -1;
- }
+ maxChargingWattage = calculateMaxChargingMicroWatt(batteryChangedIntent);
}
/** Determine whether the device is plugged. */
@@ -126,7 +115,7 @@
/** Determine whether the device is plugged in dock. */
public boolean isPluggedInDock() {
- return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+ return isPluggedInDock(plugged);
}
/**
@@ -140,15 +129,15 @@
/** Whether battery is low and needs to be charged. */
public boolean isBatteryLow() {
- return level < LOW_BATTERY_THRESHOLD;
+ return isLowBattery(level);
}
/** Whether battery defender is enabled. */
public boolean isBatteryDefender() {
- return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ return isBatteryDefender(chargingStatus);
}
- /** Return current chargin speed is fast, slow or normal. */
+ /** Return current charging speed is fast, slow or normal. */
public final int getChargingSpeed(Context context) {
final int slowThreshold = context.getResources().getInteger(
R.integer.config_chargingSlowlyThreshold);
@@ -218,4 +207,126 @@
|| plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
|| plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
}
+
+ /** Determine whether the device is plugged in dock. */
+ public static boolean isPluggedInDock(Intent batteryChangedIntent) {
+ return isPluggedInDock(
+ batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, BATTERY_PLUGGED_NONE));
+ }
+
+ /** Determine whether the device is plugged in dock. */
+ public static boolean isPluggedInDock(int plugged) {
+ return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+ }
+
+ /**
+ * Whether the battery is low or not.
+ *
+ * @param batteryChangedIntent the {@link ACTION_BATTERY_CHANGED} intent
+ * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isLowBattery(Intent batteryChangedIntent) {
+ int level = getBatteryLevel(batteryChangedIntent);
+ return isLowBattery(level);
+ }
+
+ /**
+ * Whether the battery is low or not.
+ *
+ * @param batteryLevel the battery level
+ * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isLowBattery(int batteryLevel) {
+ return batteryLevel <= LOW_BATTERY_THRESHOLD;
+ }
+
+ /**
+ * Whether the battery is severe low or not.
+ *
+ * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+ * @return {@code true} if the battery level is less or equal to {@link
+ * SEVERE_LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
+ int level = getBatteryLevel(batteryChangedIntent);
+ return level <= SEVERE_LOW_BATTERY_THRESHOLD;
+ }
+
+ /**
+ * Whether the battery is extreme low or not.
+ *
+ * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+ * @return {@code true} if the battery level is less or equal to {@link
+ * EXTREME_LOW_BATTERY_THRESHOLD}
+ */
+ public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
+ int level = getBatteryLevel(batteryChangedIntent);
+ return level <= EXTREME_LOW_BATTERY_THRESHOLD;
+ }
+
+ /**
+ * Whether the battery defender is enabled or not.
+ *
+ * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+ * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+ * defend, or temp defend
+ */
+ public static boolean isBatteryDefender(Intent batteryChangedIntent) {
+ int chargingStatus =
+ batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
+ return isBatteryDefender(chargingStatus);
+ }
+
+ /**
+ * Whether the battery defender is enabled or not.
+ *
+ * @param chargingStatus for {@link EXTRA_CHARGING_STATUS} field in the ACTION_BATTERY_CHANGED
+ * intent
+ * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+ * defend, or temp defend
+ */
+ public static boolean isBatteryDefender(int chargingStatus) {
+ return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ }
+
+ /**
+ * Gets the max charging current and max charging voltage form {@link
+ * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
+ * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
+ *
+ * @param context the application context
+ * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
+ * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
+ * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
+ */
+ public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
+ final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
+ if (maxChargingMicroWatt <= 0) {
+ return CHARGING_UNKNOWN;
+ } else if (maxChargingMicroWatt
+ < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
+ return CHARGING_SLOWLY;
+ } else if (maxChargingMicroWatt
+ > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+ return CHARGING_FAST;
+ } else {
+ return CHARGING_REGULAR;
+ }
+ }
+
+ private static int calculateMaxChargingMicroWatt(Intent batteryChangedIntent) {
+ final int maxChargingMicroAmp =
+ batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+ int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+ if (maxChargingMicroVolt <= 0) {
+ maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+ }
+
+ if (maxChargingMicroAmp > 0) {
+ // Calculating µW = mA * mV
+ return (int) Math.round(maxChargingMicroAmp * 0.001 * maxChargingMicroVolt * 0.001);
+ } else {
+ return -1;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index a03acc3..73f6db6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -325,7 +325,7 @@
return batteryLevel
}
- override fun onBoundsChange(bounds: Rect?) {
+ override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
updateSize()
}
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
new file mode 100644
index 0000000..6c0c1a7
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.fuelgague
+
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import android.os.BatteryManager.BATTERY_PLUGGED_AC
+import android.os.BatteryManager.BATTERY_PLUGGED_DOCK
+import android.os.BatteryManager.BATTERY_PLUGGED_USB
+import android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS
+import android.os.BatteryManager.BATTERY_STATUS_FULL
+import android.os.BatteryManager.BATTERY_STATUS_UNKNOWN
+import android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
+import android.os.BatteryManager.CHARGING_POLICY_DEFAULT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE
+import android.os.OsProtoEnums.BATTERY_PLUGGED_NONE
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.fuelgauge.BatteryStatus
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_FAST
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_REGULAR
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
+import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Suite
+import org.junit.runners.Suite.SuiteClasses
+
+@RunWith(Suite::class)
+@SuiteClasses(
+ BatteryStatusTest.NonParameterizedTest::class,
+ BatteryStatusTest.IsPluggedInTest::class,
+ BatteryStatusTest.IsChargedTest::class,
+ BatteryStatusTest.GetChargingSpeedTest::class,
+ BatteryStatusTest.IsPluggedInDockTest::class,
+)
+open class BatteryStatusTest {
+
+ @RunWith(AndroidJUnit4::class)
+ class NonParameterizedTest : BatteryStatusTest() {
+ @Test
+ fun isLowBattery_20Percent_returnsTrue() {
+ val level = 20
+ val intent = createIntent(batteryLevel = level)
+
+ assertWithMessage("failed by isLowBattery(Intent), level=$level")
+ .that(BatteryStatus.isLowBattery(intent))
+ .isTrue()
+ assertWithMessage("failed by isLowBattery($level)")
+ .that(BatteryStatus.isLowBattery(level))
+ .isTrue()
+ }
+
+ @Test
+ fun isLowBattery_21Percent_returnsFalse() {
+ val level = 21
+ val intent = createIntent(batteryLevel = level)
+
+ assertWithMessage("failed by isLowBattery(intent), level=$level")
+ .that(BatteryStatus.isLowBattery(intent))
+ .isFalse()
+ assertWithMessage("failed by isLowBattery($level)")
+ .that(BatteryStatus.isLowBattery(intent))
+ .isFalse()
+ }
+
+ @Test
+ fun isSevereLowBattery_10Percent_returnsTrue() {
+ val batteryChangedIntent = createIntent(batteryLevel = 10)
+
+ assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isTrue()
+ }
+
+ @Test
+ fun isSevereLowBattery_11Percent_returnFalse() {
+ val batteryChangedIntent = createIntent(batteryLevel = 11)
+
+ assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isFalse()
+ }
+
+ @Test
+ fun isExtremeLowBattery_3Percent_returnsTrue() {
+ val batteryChangedIntent = createIntent(batteryLevel = 3)
+
+ assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isTrue()
+ }
+
+ @Test
+ fun isExtremeLowBattery_4Percent_returnsFalse() {
+ val batteryChangedIntent = createIntent(batteryLevel = 4)
+
+ assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isFalse()
+ }
+
+ @Test
+ fun isBatteryDefender_chargingLongLife_returnsTrue() {
+ val chargingStatus = CHARGING_POLICY_ADAPTIVE_LONGLIFE
+ val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+ assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isTrue()
+ }
+
+ @Test
+ fun isBatteryDefender_nonChargingLongLife_returnsFalse() {
+ val chargingStatus = CHARGING_POLICY_DEFAULT
+ val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+ assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isFalse()
+ }
+
+ private fun assertIsBatteryDefender(chargingStatus: Int, batteryChangedIntent: Intent) =
+ object {
+ val assertions =
+ listOf(
+ "failed by isBatteryDefender(Intent), chargingStatus=$chargingStatus".let {
+ assertWithMessage(it).that(isBatteryDefender(batteryChangedIntent))
+ },
+ "failed by isBatteryDefender($chargingStatus)".let {
+ assertWithMessage(it).that(isBatteryDefender(chargingStatus))
+ },
+ )
+
+ fun isTrue() = assertions.forEach { it.isTrue() }
+
+ fun isFalse() = assertions.forEach { it.isFalse() }
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class IsPluggedInTest(
+ private val name: String,
+ private val plugged: Int,
+ val expected: Boolean
+ ) : BatteryStatusTest() {
+
+ @Test
+ fun isPluggedIn_() {
+ val batteryChangedIntent = createIntent(plugged = plugged)
+
+ assertWithMessage("failed by isPluggedIn(plugged=$plugged)")
+ .that(BatteryStatus.isPluggedIn(plugged))
+ .isEqualTo(expected)
+ assertWithMessage("failed by isPlugged(Intent), which plugged=$plugged")
+ .that(BatteryStatus.isPluggedIn(batteryChangedIntent))
+ .isEqualTo(expected)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, true),
+ arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+ arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, true),
+ arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, true),
+ arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+ )
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class IsPluggedInDockTest(
+ private val name: String,
+ private val plugged: Int,
+ val expected: Boolean
+ ) : BatteryStatusTest() {
+
+ @Test
+ fun isPluggedDockIn_() {
+ val batteryChangedIntent = createIntent(plugged = plugged)
+
+ assertWithMessage("failed by isPluggedInDock(plugged=$plugged)")
+ .that(BatteryStatus.isPluggedInDock(plugged))
+ .isEqualTo(expected)
+ assertWithMessage("failed by isPluggedInDock(Intent), which plugged=$plugged")
+ .that(BatteryStatus.isPluggedInDock(batteryChangedIntent))
+ .isEqualTo(expected)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, false),
+ arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+ arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, false),
+ arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, false),
+ arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+ )
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class IsChargedTest(
+ private val status: Int,
+ private val batteryLevel: Int,
+ private val expected: Boolean
+ ) : BatteryStatusTest() {
+
+ @Test
+ fun isCharged_() {
+ val batteryChangedIntent = createIntent(batteryLevel = batteryLevel, status = status)
+
+ assertWithMessage(
+ "failed by isCharged(Intent), status=$status, batteryLevel=$batteryLevel"
+ )
+ .that(BatteryStatus.isCharged(batteryChangedIntent))
+ .isEqualTo(expected)
+ assertWithMessage("failed by isCharged($status, $batteryLevel)")
+ .that(BatteryStatus.isCharged(status, batteryLevel))
+ .isEqualTo(expected)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "status{0}_level{1}_returns-{2}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf(BATTERY_STATUS_FULL, 99, true),
+ arrayOf(BATTERY_STATUS_UNKNOWN, 100, true),
+ arrayOf(BATTERY_STATUS_FULL, 100, true),
+ arrayOf(BATTERY_STATUS_UNKNOWN, 99, false),
+ )
+ }
+ }
+
+ @RunWith(Parameterized::class)
+ class GetChargingSpeedTest(
+ private val name: String,
+ private val maxChargingCurrent: Optional<Int>,
+ private val maxChargingVoltage: Optional<Int>,
+ private val expectedChargingSpeed: Int,
+ ) {
+
+ val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun getChargingSpeed_() {
+ val batteryChangedIntent =
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
+ maxChargingVoltage.ifPresent { putExtra(EXTRA_MAX_CHARGING_VOLTAGE, it) }
+ }
+
+ assertThat(BatteryStatus.getChargingSpeed(context, batteryChangedIntent))
+ .isEqualTo(expectedChargingSpeed)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() =
+ arrayListOf(
+ arrayOf(
+ "maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
+ Optional.empty<Int>(),
+ Optional.empty<Int>(),
+ CHARGING_UNKNOWN
+ ),
+ arrayOf(
+ "maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
+ Optional.of(0),
+ Optional.of(0),
+ CHARGING_UNKNOWN
+ ),
+ arrayOf(
+ "maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
+ Optional.of(1500000),
+ Optional.of(5000000),
+ CHARGING_REGULAR
+ ),
+ arrayOf(
+ "maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
+ Optional.of(1000000),
+ Optional.of(5000000),
+ CHARGING_REGULAR
+ ),
+ arrayOf(
+ "maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
+ Optional.of(1501000),
+ Optional.of(5000000),
+ CHARGING_FAST
+ ),
+ arrayOf(
+ "maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
+ Optional.of(999999),
+ Optional.of(5000000),
+ CHARGING_SLOWLY
+ ),
+ )
+ }
+ }
+
+ protected fun createIntent(
+ batteryLevel: Int = 50,
+ chargingStatus: Int = CHARGING_POLICY_DEFAULT,
+ plugged: Int = BATTERY_PLUGGED_NONE,
+ status: Int = BatteryManager.BATTERY_STATUS_CHARGING,
+ ): Intent =
+ Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+ putExtra(BatteryManager.EXTRA_STATUS, status)
+ putExtra(BatteryManager.EXTRA_LEVEL, batteryLevel)
+ putExtra(BatteryManager.EXTRA_SCALE, 100)
+ putExtra(BatteryManager.EXTRA_CHARGING_STATUS, chargingStatus)
+ putExtra(BatteryManager.EXTRA_PLUGGED, plugged)
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 9da1ab8..27a45df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -44,6 +44,7 @@
import com.android.internal.app.LocalePicker;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
@@ -332,21 +333,30 @@
* @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
*/
private void setRingtone(String name, String value) {
- // If it's null, don't change the default
- if (value == null) return;
- final Uri ringtoneUri;
- if (SILENT_RINGTONE.equals(value)) {
- ringtoneUri = null;
- } else {
- Uri canonicalUri = Uri.parse(value);
- ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
- if (ringtoneUri == null) {
- // Unrecognized or invalid Uri, don't restore
- return;
- }
- }
- final int ringtoneType = getRingtoneType(name);
+ Log.v(TAG, "Set ringtone for name: " + name + " value: " + value);
+ // If it's null, don't change the default.
+ if (value == null) return;
+ final int ringtoneType = getRingtoneType(name);
+ if (SILENT_RINGTONE.equals(value)) {
+ // SILENT_RINGTONE is a special constant generated by onBackupValue in the source
+ // device.
+ RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, null);
+ return;
+ }
+
+ Uri ringtoneUri = null;
+ try {
+ ringtoneUri =
+ RingtoneManager.getRingtoneUriForRestore(
+ mContext.getContentResolver(), value, ringtoneType);
+ } catch (FileNotFoundException | IllegalArgumentException e) {
+ Log.w(TAG, "Failed to resolve " + value + ": " + e);
+ // Unrecognized or invalid Uri, don't restore
+ return;
+ }
+
+ Log.v(TAG, "setActualDefaultRingtoneUri type: " + ringtoneType + ", uri: " + ringtoneUri);
RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index bc81c44..ef062df 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -26,15 +26,24 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.LocaleList;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +66,13 @@
private static final String SETTING_VALUE = "setting_value";
private static final String SETTING_REAL_VALUE = "setting_real_value";
+ private static final String DEFAULT_RINGTONE_VALUE =
+ "content://media/internal/audio/media/10?title=DefaultRingtone&canonical=1";
+ private static final String DEFAULT_NOTIFICATION_VALUE =
+ "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
+ private static final String DEFAULT_ALARM_VALUE =
+ "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
+
private SettingsHelper mSettingsHelper;
@Mock private Context mContext;
@@ -74,6 +90,7 @@
mTelephonyManager);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getApplicationContext()).thenReturn(mContext);
+ when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
when(mContext.getContentResolver()).thenReturn(getContentResolver());
mSettingsHelper = spy(new SettingsHelper(mContext));
@@ -338,6 +355,377 @@
}
@Test
+ public void testRestoreValue_customRingtone_regularUncanonicalize_Success() {
+ final String sourceRingtoneValue =
+ "content://media/internal/audio/media/1?title=Song&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://media/internal/audio/media/100";
+ final String newRingtoneValueCanonicalized =
+ "content://media/internal/audio/media/100?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(sourceRingtoneValue));
+ return Uri.parse(newRingtoneValueUncanonicalized);
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/1?title=Song&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/100";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/100?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {100L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_ringtone=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"Song"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_notificationSound_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/2?title=notificationPing&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/200";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {200L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_notification=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"notificationPing"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.NOTIFICATION_SOUND,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(
+ Settings.System.getString(
+ mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_alarmSound_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/3?title=alarmSound&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/300";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {300L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_alarm=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.ALARM_ALERT,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_useCustomLookup_multipleResults_notRestore() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/1?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ // This is to mock the case that there are multiple results by querying title +
+ // ringtone_type.
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {100L});
+ cursor.addRow(new Object[] {110L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_restoreSilentValue() {
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ "_silent",
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(null);
+ }
+
+ public static class MockSettingsProvider extends MockContentProvider {
+ ContentResolver mBaseContentResolver;
+
+ public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+ super(context);
+ this.mBaseContentResolver = baseContentResolver;
+ }
+
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+ }
+ }
+
+ @Test
public void restoreValue_autoRotation_deviceStateAutoRotationDisabled_restoresValue() {
when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
.thenReturn(new String[]{});
@@ -400,4 +788,20 @@
Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
}
+
+ private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+ Settings.System.putString(
+ contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+ Settings.System.putString(
+ contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+ Settings.System.putString(
+ contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+
+ assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+ assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+ .isEqualTo(DEFAULT_NOTIFICATION_VALUE);
+ assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+ .isEqualTo(DEFAULT_ALARM_VALUE);
+ }
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 37b1ee5..187d073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -249,7 +249,7 @@
// intent is to launch a dialog from another dialog.
val animatedParent =
openedDialogs.firstOrNull {
- it.dialog.window.decorView.viewRootImpl == controller.viewRoot
+ it.dialog.window?.decorView?.viewRootImpl == controller.viewRoot
}
val controller =
animatedParent?.dialogContentWithBackground?.let {
@@ -336,7 +336,7 @@
): ActivityLaunchAnimator.Controller? {
val animatedDialog =
openedDialogs.firstOrNull {
- it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+ it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
}
?: return null
return createActivityLaunchController(animatedDialog, cujType)
@@ -417,7 +417,7 @@
animatedDialog.prepareForStackDismiss()
// Remove the dim.
- dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
@@ -783,7 +783,7 @@
}
// Show the background dim.
- dialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
startAnimation(
isLaunching = true,
@@ -863,7 +863,7 @@
isLaunching = false,
onLaunchAnimationStart = {
// Remove the dim background as soon as we start the animation.
- dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
},
onLaunchAnimationEnd = {
val dialogContentWithBackground = this.dialogContentWithBackground!!
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 1a03ede..6c4b695 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -206,8 +206,9 @@
return
}
- backgroundView = FrameLayout(launchContainer.context)
- launchContainerOverlay.add(backgroundView)
+ backgroundView = FrameLayout(launchContainer.context).also {
+ launchContainerOverlay.add(it)
+ }
// We wrap the ghosted view background and use it to draw the expandable background. Its
// alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -319,7 +320,7 @@
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView)
- launchContainerOverlay.remove(backgroundView)
+ backgroundView?.let { launchContainerOverlay.remove(it) }
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 142fd21..d6eba2e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -283,7 +283,7 @@
animator.addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+ override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
if (DEBUG) {
Log.d(TAG, "Animation started")
}
@@ -295,7 +295,7 @@
launchContainerOverlay.add(windowBackgroundLayer)
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index b555fa5..8dc7495 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,7 +42,9 @@
return baseTypeface
}
- val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
+ val axes = FontVariationAxis.fromFontVariationSettings(fVar)
+ ?.toMutableList()
+ ?: mutableListOf()
axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
if (axes.isEmpty()) {
return baseTypeface
@@ -120,8 +122,8 @@
}
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
- override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+ override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase()
+ override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
}
)
}
@@ -302,11 +304,11 @@
if (onAnimationEnd != null) {
val listener =
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
onAnimationEnd.run()
animator.removeListener(this)
}
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
animator.removeListener(this)
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 38b99cc..bd3706e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -1046,7 +1046,7 @@
}
}
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
cancelled = true
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 46f5971..92d2bd2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -190,6 +190,9 @@
/** Flag denoting transit clock are enabled in wallpaper picker. */
const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
+ /** Flag denoting adding apply button to wallpaper picker's grid preview page. */
+ const val FLAG_NAME_GRID_APPLY_BUTTON = "wallpaper_picker_grid_apply_button"
+
/** Flag denoting whether preview loading animation is enabled. */
const val FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION =
"wallpaper_picker_preview_animation"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index f83fa33..affb76b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -57,7 +57,7 @@
private fun readIntFromBundle(extras: Bundle, key: String): Int? =
try {
- extras.getString(key).toInt()
+ extras.getString(key)?.toInt()
} catch (e: Exception) {
null
}
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..cd7ab98
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/keyguard_pin_view_landscape" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..80cc8c0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/keyguard_pin_view_portrait" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
new file mode 100644
index 0000000..e00742d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, 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.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/keyguard_pin_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area" />
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toTopOf="@+id/row0"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintVertical_chainStyle="packed" />
+
+ <!-- Set this to be just above key1. It would be better to introduce a barrier above
+ key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+ drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+ case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+ fine. -->
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/row0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintTop_toBottomOf="@+id/bouncer_message_view"
+ tools:layout_editor_absoluteX="-16dp">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/pinEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pin_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+ updated in KeyguardPINView to reduce the height of the PIN pad. -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pin_pad_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ androidprv:layout_constraintGuide_percent="0" />
+
+ <com.android.keyguard.KeyguardPinFlowView
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+
+ androidprv:flow_verticalBias="0.5"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/pinEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/pinEntry" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7e892f7..d85e012 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -21,9 +21,11 @@
<dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
<!-- padding for container with status icons and battery -->
- <dimen name="status_bar_icons_padding_end">12dp</dimen>
+ <dimen name="status_bar_icons_padding_end">4dp</dimen>
<!-- start padding is smaller to account for status icon margins coming from drawable itself -->
- <dimen name="status_bar_icons_padding_start">11dp</dimen>
+ <dimen name="status_bar_icons_padding_start">3dp</dimen>
+ <dimen name="status_bar_icons_padding_bottom">2dp</dimen>
+ <dimen name="status_bar_icons_padding_top">2dp</dimen>
<dimen name="status_bar_padding_end">0dp</dimen>
@@ -78,8 +80,8 @@
<dimen name="large_screen_shade_header_height">42dp</dimen>
<!-- start padding is smaller to account for status icon margins coming from drawable itself -->
- <dimen name="shade_header_system_icons_padding_start">11dp</dimen>
- <dimen name="shade_header_system_icons_padding_end">12dp</dimen>
+ <dimen name="shade_header_system_icons_padding_start">3dp</dimen>
+ <dimen name="shade_header_system_icons_padding_end">4dp</dimen>
<!-- Lockscreen shade transition values -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index d74eca6..dc1f0e4 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,9 +16,6 @@
*/
-->
<resources>
- <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
- <dimen name="status_bar_icons_padding_start">10dp</dimen>
-
<!-- gap on either side of status bar notification icons -->
<dimen name="status_bar_icon_horizontal_margin">1sp</dimen>
@@ -30,9 +27,6 @@
<dimen name="large_screen_shade_header_height">56dp</dimen>
- <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
- <dimen name="shade_header_system_icons_padding_start">10dp</dimen>
-
<!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
<dimen name="biometric_auth_pattern_view_size">348dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a056445..55978e6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -351,9 +351,9 @@
<!-- paddings for container with status icons and battery -->
<!-- padding start is a bit smaller than end to account for status icon margin-->
- <dimen name="status_bar_icons_padding_start">11dp</dimen>
+ <dimen name="status_bar_icons_padding_start">3dp</dimen>
- <dimen name="status_bar_icons_padding_end">0dp</dimen>
+ <dimen name="status_bar_icons_padding_end">4dp</dimen>
<dimen name="status_bar_icons_padding_bottom">0dp</dimen>
<dimen name="status_bar_icons_padding_top">0dp</dimen>
@@ -364,7 +364,7 @@
<dimen name="status_bar_padding_start">8dp</dimen>
<!-- the padding on the end of the statusbar -->
- <dimen name="status_bar_padding_end">8dp</dimen>
+ <dimen name="status_bar_padding_end">4dp</dimen>
<!-- the padding on the top of the statusbar (usually 0) -->
<dimen name="status_bar_padding_top">0dp</dimen>
@@ -1607,7 +1607,7 @@
<!-- Status bar user chip -->
<dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
<!-- below also works as break between user chip and hover state of status icons -->
- <dimen name="status_bar_user_chip_end_margin">4dp</dimen>
+ <dimen name="status_bar_user_chip_end_margin">8dp</dimen>
<dimen name="status_bar_user_chip_text_size">12sp</dimen>
<!-- System UI Dialog -->
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c2dba6c..261b08d 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -40,4 +40,7 @@
<!-- Whether face auth will immediately stop when the display state is OFF -->
<bool name="flag_stop_face_auth_on_display_off">false</bool>
+
+ <!-- Whether we want to stop pulsing while running the face scanning animation -->
+ <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ea8df8..cddfda2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2406,6 +2406,8 @@
<string name="magnification_open_settings_click_label">Open magnification settings</string>
<!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
<string name="magnification_close_settings_click_label">Close magnification settings</string>
+ <!-- Click action label for exiting magnifier edit mode. [CHAR LIMIT=NONE] -->
+ <string name="magnification_exit_edit_mode_click_label">Exit edit mode</string>
<!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
<string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
index c142933..5edd283 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
@@ -27,6 +27,6 @@
*/
fun ActivityManager.isInForeground(packageName: String): Boolean {
val tasks: List<ActivityManager.RunningTaskInfo> = getRunningTasks(1)
- return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+ return tasks.isNotEmpty() && packageName == tasks[0].topActivity?.packageName
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
index d7e61d6..ebc57d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -31,15 +31,15 @@
var visibleOnScreen = false
constructor(parcel: Parcel) : this() {
- this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
+ this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader) ?: Rect()
this.selectedPage = parcel.readInt()
this.visibleOnScreen = parcel.readBoolean()
}
- override fun writeToParcel(dest: Parcel?, flags: Int) {
- dest?.writeParcelable(boundsOnScreen, 0)
- dest?.writeInt(selectedPage)
- dest?.writeBoolean(visibleOnScreen)
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeParcelable(boundsOnScreen, 0)
+ dest.writeInt(selectedPage)
+ dest.writeBoolean(visibleOnScreen)
}
override fun describeContents(): Int {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index aca9907..dac130d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -39,7 +39,7 @@
fun init() {
rotationChangeProvider.addCallback(rotationListener)
- rotationListener.onRotationChanged(context.display.rotation)
+ context.display?.rotation?.let { rotationListener.onRotationChanged(it) }
}
private val rotationListener = RotationListener { rotation ->
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 78a5c98..495367b 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -105,7 +105,7 @@
hideAnimator.addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
super@BouncerKeyguardMessageArea.setMessage(msg, animate)
}
}
@@ -118,7 +118,7 @@
showAnimator.addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
textAboutToShow = null
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 4a6e53d..4f4eec6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -144,7 +144,7 @@
smallClockOnAttachStateChangeListener =
object : OnAttachStateChangeListener {
var pastVisibility: Int? = null
- override fun onViewAttachedToWindow(view: View?) {
+ override fun onViewAttachedToWindow(view: View) {
value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
if (view != null) {
smallClockFrame = view.parent as FrameLayout
@@ -168,7 +168,7 @@
}
}
- override fun onViewDetachedFromWindow(p0: View?) {
+ override fun onViewDetachedFromWindow(p0: View) {
smallClockFrame?.viewTreeObserver
?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
@@ -178,10 +178,10 @@
largeClockOnAttachStateChangeListener =
object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(p0: View?) {
+ override fun onViewAttachedToWindow(p0: View) {
value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
}
- override fun onViewDetachedFromWindow(p0: View?) {
+ override fun onViewDetachedFromWindow(p0: View) {
}
}
value.largeClock.view
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 42a4e72..42dceb8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -100,6 +100,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -666,6 +667,11 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mViewMediatorCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 4e1cbc7..f9f9883 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -33,7 +33,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.media.AudioManager;
@@ -69,6 +68,7 @@
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -81,8 +81,6 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneKey;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -143,7 +141,7 @@
private Runnable mCancelAction;
private boolean mWillRunDismissFromKeyguard;
- private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+ private int mLastOrientation;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
@@ -349,7 +347,14 @@
@Override
public void onDensityOrFontScaleChanged() {
- KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+ KeyguardSecurityContainerController.this
+ .onDensityOrFontScaleOrOrientationChanged();
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ KeyguardSecurityContainerController.this
+ .onDensityOrFontScaleOrOrientationChanged();
}
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -388,7 +393,7 @@
}
};
private final UserInteractor mUserInteractor;
- private final Provider<SceneInteractor> mSceneInteractor;
+ private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
@Nullable private Job mSceneTransitionCollectionJob;
@@ -419,7 +424,7 @@
Provider<JavaAdapter> javaAdapter,
UserInteractor userInteractor,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
- Provider<SceneInteractor> sceneInteractor
+ Provider<AuthenticationInteractor> authenticationInteractor
) {
super(view);
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -448,7 +453,7 @@
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
mUserInteractor = userInteractor;
- mSceneInteractor = sceneInteractor;
+ mAuthenticationInteractor = authenticationInteractor;
mJavaAdapter = javaAdapter;
}
@@ -474,19 +479,21 @@
showPrimarySecurityScreen(false);
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+ // When the scene framework says that the lockscreen has been dismissed, dismiss the
+ // keyguard here, revealing the underlying app or launcher:
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mSceneInteractor.get().finishedSceneTransitions(
- /* from= */ SceneKey.Bouncer.INSTANCE,
- /* to= */ SceneKey.Gone.INSTANCE),
- unused -> {
- final int selectedUserId = mUserInteractor.getSelectedUserId();
- showNextSecurityScreenOrFinish(
+ mAuthenticationInteractor.get().isLockscreenDismissed(),
+ isLockscreenDismissed -> {
+ if (isLockscreenDismissed) {
+ final int selectedUserId = mUserInteractor.getSelectedUserId();
+ showNextSecurityScreenOrFinish(
/* authenticated= */ true,
selectedUserId,
/* bypassSecondaryLockScreen= */ true,
mSecurityModel.getSecurityMode(selectedUserId));
- });
+ }
+ }
+ );
}
}
@@ -833,8 +840,7 @@
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser());
-
- if (securityMode == SecurityMode.None) {
+ if (securityMode == SecurityMode.None || isLockscreenDisabled) {
finish = isLockscreenDisabled;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
@@ -1154,7 +1160,7 @@
}
/** Handles density or font scale changes. */
- private void onDensityOrFontScaleChanged() {
+ private void onDensityOrFontScaleOrOrientationChanged() {
reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 7585279..5774e42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -23,12 +23,14 @@
import android.os.Build;
import android.os.Trace;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.GridLayout;
import com.android.systemui.R;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.CrossFadeHelper;
import java.io.PrintWriter;
@@ -110,6 +112,11 @@
}
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 1a0c7f9..8611dbbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,6 +22,7 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -154,11 +155,16 @@
}
float getLocationTop() {
- return mLockIconCenter.y - mRadius;
+ Rect r = new Rect();
+ mLockIcon.getGlobalVisibleRect(r);
+ return r.top;
}
float getLocationBottom() {
- return mLockIconCenter.y + mRadius;
+ Rect r = new Rect();
+ mLockIcon.getGlobalVisibleRect(r);
+ return r.bottom;
+
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index a04a48d..e773416 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -58,6 +58,7 @@
private float mStartRadius;
private float mEndRadius;
private int mHeight;
+ private int mWidth;
private static final int EXPAND_ANIMATION_MS = 100;
private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -95,11 +96,17 @@
mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress);
int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress);
int difference = mHeight - height;
- mBackground.setBounds(0, difference / 2, mHeight, mHeight - difference / 2);
+
+ int left = 0;
+ int top = difference / 2;
+ int right = mWidth;
+ int bottom = mHeight - difference / 2;
+ mBackground.setBounds(left, top, right, bottom);
}
- void onLayout(int height) {
+ void onLayout(int width, int height) {
boolean shouldUpdateHeight = height != mHeight;
+ mWidth = width;
mHeight = height;
mStartRadius = height / 2f;
mEndRadius = height / 4f;
@@ -121,7 +128,7 @@
ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
@SuppressLint("ResourceType") TypedArray a = ctw.obtainStyledAttributes(customAttrs);
mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
- NUM_PAD_BACKGROUND);
+ NUM_PAD_BACKGROUND);
a.recycle();
mPressedBackgroundColor = getColorAttrDefaultColor(context, NUM_PAD_BACKGROUND_PRESSED);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3f1741a6..5c2f3b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -74,8 +74,9 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
-
- if (mAnimator != null) mAnimator.onLayout(b - t);
+ int width = r - l;
+ int height = b - t;
+ if (mAnimator != null) mAnimator.onLayout(width, height);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index edc298c..466d154 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -211,7 +211,9 @@
left = centerX - mKlondikeText.getMeasuredWidth() / 2;
mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
- if (mAnimator != null) mAnimator.onLayout(b - t);
+ int width = r - l;
+ int height = b - t;
+ if (mAnimator != null) mAnimator.onLayout(width, height);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 04acd0b..27b9056 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -131,14 +131,14 @@
import com.android.systemui.util.leak.LeakReporter;
import com.android.systemui.util.sensors.AsyncSensorManager;
-import dagger.Lazy;
-
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
+import dagger.Lazy;
+
/**
* Class to handle ugly dependencies throughout sysui until we determine the
* long-term dependency injection solution.
@@ -280,7 +280,6 @@
@Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
@Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
@Inject Lazy<TunablePaddingService> mTunablePaddingService;
- @Inject Lazy<ForegroundServiceController> mForegroundServiceController;
@Inject Lazy<UiOffloadThread> mUiOffloadThread;
@Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
@Inject Lazy<LightBarController> mLightBarController;
@@ -458,8 +457,6 @@
mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
- mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get);
-
mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 403c809..95e2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -36,6 +36,8 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.asIndenting
@@ -54,6 +56,7 @@
val mainExecutor: Executor,
val logger: ScreenDecorationsLogger,
val authController: AuthController,
+ val featureFlags: FeatureFlags,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -294,6 +297,15 @@
}
private fun createFaceScanningRimAnimator(): AnimatorSet {
+ val dontPulse = featureFlags.isEnabled(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION)
+ if (dontPulse) {
+ return AnimatorSet().apply {
+ playSequentially(
+ cameraProtectionAnimator,
+ createRimAppearAnimator(),
+ )
+ }
+ }
return AnimatorSet().apply {
playSequentially(
cameraProtectionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
deleted file mode 100644
index 15e8c4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ /dev/null
@@ -1,183 +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.systemui;
-
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.Assert;
-
-import javax.inject.Inject;
-
-/**
- * Tracks state of foreground services and notifications related to foreground services per user.
- */
-@SysUISingleton
-public class ForegroundServiceController {
- public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
-
- private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
- private final Object mMutex = new Object();
- private final Handler mMainHandler;
-
- @Inject
- public ForegroundServiceController(
- AppOpsController appOpsController,
- @Main Handler mainHandler) {
- mMainHandler = mainHandler;
- appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
- mMainHandler.post(() -> {
- onAppOpChanged(code, uid, packageName, active);
- });
- });
- }
-
- /**
- * @return true if this user has services missing notifications and therefore needs a
- * disclosure notification for running a foreground service.
- */
- public boolean isDisclosureNeededForUser(int userId) {
- synchronized (mMutex) {
- final ForegroundServicesUserState services = mUserServices.get(userId);
- if (services == null) return false;
- return services.isDisclosureNeeded();
- }
- }
-
- /**
- * @return true if this user/pkg has a missing or custom layout notification and therefore needs
- * a disclosure notification showing the user which appsOps the app is using.
- */
- public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
- synchronized (mMutex) {
- final ForegroundServicesUserState services = mUserServices.get(userId);
- if (services == null) return false;
- return services.getStandardLayoutKeys(pkg) == null;
- }
- }
-
- /**
- * Gets active app ops for this user and package
- */
- @Nullable
- public ArraySet<Integer> getAppOps(int userId, String pkg) {
- synchronized (mMutex) {
- final ForegroundServicesUserState services = mUserServices.get(userId);
- if (services == null) {
- return null;
- }
- return services.getFeatures(pkg);
- }
- }
-
- /**
- * Records active app ops and updates the app op for the pending or visible notifications
- * with the given parameters.
- * App Ops are stored in FSC in addition to NotificationEntry in case they change before we
- * have a notification to tag.
- * @param appOpCode code for appOp to add/remove
- * @param uid of user the notification is sent to
- * @param packageName package that created the notification
- * @param active whether the appOpCode is active or not
- */
- void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
- Assert.isMainThread();
-
- int userId = UserHandle.getUserId(uid);
- // Record active app ops
- synchronized (mMutex) {
- ForegroundServicesUserState userServices = mUserServices.get(userId);
- if (userServices == null) {
- userServices = new ForegroundServicesUserState();
- mUserServices.put(userId, userServices);
- }
- if (active) {
- userServices.addOp(packageName, appOpCode);
- } else {
- userServices.removeOp(packageName, appOpCode);
- }
- }
- }
-
- /**
- * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
- * the given {@link UserStateUpdateCallback} on it. If no state exists for the user ID, creates
- * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
- * If {@code createIfNotFound} is false, no update is performed.
- *
- * @return false if no user state was found and none was created; true otherwise.
- */
- boolean updateUserState(int userId,
- UserStateUpdateCallback updateCallback,
- boolean createIfNotFound) {
- synchronized (mMutex) {
- ForegroundServicesUserState userState = mUserServices.get(userId);
- if (userState == null) {
- if (createIfNotFound) {
- userState = new ForegroundServicesUserState();
- mUserServices.put(userId, userState);
- } else {
- return false;
- }
- }
- return updateCallback.updateUserState(userState);
- }
- }
-
- /**
- * @return true if {@code sbn} is the system-provided disclosure notification containing the
- * list of running foreground services.
- */
- public boolean isDisclosureNotification(StatusBarNotification sbn) {
- return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
- && sbn.getTag() == null
- && sbn.getPackageName().equals("android");
- }
-
- /**
- * @return true if sbn is one of the window manager "drawing over other apps" notifications
- */
- public boolean isSystemAlertNotification(StatusBarNotification sbn) {
- return sbn.getPackageName().equals("android")
- && sbn.getTag() != null
- && sbn.getTag().contains("AlertWindowNotification");
- }
-
- /**
- * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
- * to perform the update.
- */
- interface UserStateUpdateCallback {
- /**
- * Perform update operations on the provided {@code userState}.
- *
- * @return true if the update succeeded.
- */
- boolean updateUserState(ForegroundServicesUserState userState);
-
- /** Called if the state was not found and was not created. */
- default void userStateNotFound(int userId) {
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
deleted file mode 100644
index a1a3b72..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import javax.inject.Inject;
-
-/** Updates foreground service notification state in response to notification data events. */
-@SysUISingleton
-public class ForegroundServiceNotificationListener {
-
- private static final String TAG = "FgServiceController";
- private static final boolean DBG = false;
-
- private final Context mContext;
- private final ForegroundServiceController mForegroundServiceController;
- private final NotifPipeline mNotifPipeline;
-
- @Inject
- public ForegroundServiceNotificationListener(Context context,
- ForegroundServiceController foregroundServiceController,
- NotifPipeline notifPipeline) {
- mContext = context;
- mForegroundServiceController = foregroundServiceController;
- mNotifPipeline = notifPipeline;
- }
-
- /** Initializes this listener by connecting it to the notification pipeline. */
- public void init() {
- mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- addNotification(entry, entry.getImportance());
- }
-
- @Override
- public void onEntryUpdated(NotificationEntry entry) {
- updateNotification(entry, entry.getImportance());
- }
-
- @Override
- public void onEntryRemoved(NotificationEntry entry, int reason) {
- removeNotification(entry.getSbn());
- }
- });
- }
-
- /**
- * @param entry notification that was just posted
- */
- private void addNotification(NotificationEntry entry, int importance) {
- updateNotification(entry, importance);
- }
-
- /**
- * @param sbn notification that was just removed
- */
- private void removeNotification(StatusBarNotification sbn) {
- mForegroundServiceController.updateUserState(
- sbn.getUserId(),
- new ForegroundServiceController.UserStateUpdateCallback() {
- @Override
- public boolean updateUserState(ForegroundServicesUserState userState) {
- if (mForegroundServiceController.isDisclosureNotification(sbn)) {
- // if you remove the dungeon entirely, we take that to mean there are
- // no running services
- userState.setRunningServices(null, 0);
- return true;
- } else {
- // this is safe to call on any notification, not just
- // FLAG_FOREGROUND_SERVICE
- return userState.removeNotification(sbn.getPackageName(), sbn.getKey());
- }
- }
-
- @Override
- public void userStateNotFound(int userId) {
- if (DBG) {
- Log.w(TAG, String.format(
- "user %d with no known notifications got removeNotification "
- + "for %s",
- sbn.getUserId(), sbn));
- }
- }
- },
- false /* don't create */);
- }
-
- /**
- * @param entry notification that was just changed in some way
- */
- private void updateNotification(NotificationEntry entry, int newImportance) {
- final StatusBarNotification sbn = entry.getSbn();
- mForegroundServiceController.updateUserState(
- sbn.getUserId(),
- userState -> {
- if (mForegroundServiceController.isDisclosureNotification(sbn)) {
- final Bundle extras = sbn.getNotification().extras;
- if (extras != null) {
- final String[] svcs = extras.getStringArray(
- Notification.EXTRA_FOREGROUND_APPS);
- userState.setRunningServices(svcs, sbn.getNotification().when);
- }
- } else {
- userState.removeNotification(sbn.getPackageName(), sbn.getKey());
- if (0 != (sbn.getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE)) {
- if (newImportance > NotificationManager.IMPORTANCE_MIN) {
- userState.addImportantNotification(sbn.getPackageName(),
- sbn.getKey());
- }
- }
- final Notification.Builder builder =
- Notification.Builder.recoverBuilder(
- mContext, sbn.getNotification());
- if (builder.usesStandardHeader()) {
- userState.addStandardLayoutNotification(
- sbn.getPackageName(), sbn.getKey());
- }
- }
- return true;
- },
- true /* create if not found */);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 602f817..28d59c2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1446,6 +1446,12 @@
private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
private CharSequence getClickAccessibilityActionLabel() {
+ if (mEditSizeEnable) {
+ // Perform click action to exit edit mode
+ return mContext.getResources().getString(
+ R.string.magnification_exit_edit_mode_click_label);
+ }
+
return mSettingsPanelVisibility
? mContext.getResources().getString(
R.string.magnification_close_settings_click_label)
@@ -1488,8 +1494,14 @@
private boolean performA11yAction(int action) {
if (action == AccessibilityAction.ACTION_CLICK.getId()) {
- // Simulate tapping the drag view so it opens the Settings.
- handleSingleTap(mDragView);
+ if (mEditSizeEnable) {
+ // When edit mode is enabled, click the magnifier to exit edit mode.
+ setEditMagnifierSizeMode(false);
+ } else {
+ // Simulate tapping the drag view so it opens the Settings.
+ handleSingleTap(mDragView);
+ }
+
} else if (action == R.id.accessibility_action_zoom_in) {
performScale(mScale + A11Y_CHANGE_SCALE_DIFFERENCE);
} else if (action == R.id.accessibility_action_zoom_out) {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index e121790..ecd7bae 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -102,7 +102,7 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
- initialValue = true,
+ initialValue = false,
)
/**
@@ -114,14 +114,18 @@
* - `true` doesn't mean the lockscreen is invisible (since this state changes before the
* transition occurs).
*/
- private val isLockscreenDismissed =
+ val isLockscreenDismissed: StateFlow<Boolean> =
sceneInteractor.desiredScene
.map { it.key }
.filter { currentScene ->
currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
}
.map { it == SceneKey.Gone }
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/**
* Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 58adfa1..58c8000 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -82,6 +82,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.PrintWriter;
@@ -288,12 +289,13 @@
@NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
@NonNull PromptViewModel promptViewModel,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
- @NonNull @Background DelayableExecutor bgExecutor) {
+ @NonNull @Background DelayableExecutor bgExecutor,
+ @NonNull VibratorHelper vibratorHelper) {
this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
jankMonitor, authBiometricFingerprintViewModelProvider, promptSelectorInteractor,
promptCredentialInteractor, promptViewModel, credentialViewModelProvider,
- new Handler(Looper.getMainLooper()), bgExecutor);
+ new Handler(Looper.getMainLooper()), bgExecutor, vibratorHelper);
}
@VisibleForTesting
@@ -314,7 +316,8 @@
@NonNull PromptViewModel promptViewModel,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
- @NonNull @Background DelayableExecutor bgExecutor) {
+ @NonNull @Background DelayableExecutor bgExecutor,
+ @NonNull VibratorHelper vibratorHelper) {
super(config.mContext);
mConfig = config;
@@ -364,7 +367,8 @@
if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) {
showPrompt(config, layoutInflater, promptViewModel,
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
- Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+ Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
+ vibratorHelper, featureFlags);
} else {
showLegacyPrompt(config, layoutInflater, fpProps, faceProps);
}
@@ -388,7 +392,10 @@
private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
@NonNull PromptViewModel viewModel,
@Nullable FingerprintSensorPropertiesInternal fpProps,
- @Nullable FaceSensorPropertiesInternal faceProps) {
+ @Nullable FaceSensorPropertiesInternal faceProps,
+ @NonNull VibratorHelper vibratorHelper,
+ @NonNull FeatureFlags featureFlags
+ ) {
if (Utils.isBiometricAllowed(config.mPromptInfo)) {
mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
config.mPromptInfo,
@@ -401,7 +408,8 @@
mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
// TODO(b/201510778): This uses the wrong timeout in some cases
getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
- mBackgroundView, mBiometricCallback, mApplicationCoroutineScope);
+ mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+ vibratorHelper, featureFlags);
// TODO(b/251476085): migrate these dependencies
if (fpProps != null && fpProps.isAnyUdfpsType()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 3df7ca5..60e4cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,9 +85,12 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -101,7 +104,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
import kotlinx.coroutines.CoroutineScope;
/**
@@ -183,6 +185,7 @@
@NonNull private final UdfpsUtils mUdfpsUtils;
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
+ @NonNull private final VibratorHelper mVibratorHelper;
@VisibleForTesting
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@@ -771,7 +774,8 @@
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
- @NonNull UdfpsUtils udfpsUtils) {
+ @NonNull UdfpsUtils udfpsUtils,
+ @NonNull VibratorHelper vibratorHelper) {
mContext = context;
mFeatureFlags = featureFlags;
mExecution = execution;
@@ -794,6 +798,7 @@
mFaceEnrolledForUser = new SparseBooleanArray();
mUdfpsUtils = udfpsUtils;
mApplicationCoroutineScope = applicationCoroutineScope;
+ mVibratorHelper = vibratorHelper;
mLogContextInteractor = logContextInteractor;
mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
@@ -1341,7 +1346,7 @@
wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
mInteractionJankMonitor, mAuthBiometricFingerprintViewModelProvider,
mPromptCredentialInteractor, mPromptSelectorInteractor, viewModel,
- mCredentialViewModelProvider, bgExecutor);
+ mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 946ddba..ea9fe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -230,7 +230,7 @@
lightRevealScrim.revealAmount = animator.animatedValue as Float
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
// Reset light reveal scrim to the default, so the CentralSurfaces
// can handle any subsequent light reveal changes
// (ie: from dozing changes)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 5ede16d..4c2dc41 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -147,12 +147,12 @@
retractDwellAnimator = AnimatorSet().apply {
playTogether(retractDwellRippleAnimator, retractAlphaAnimator)
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
dwellPulseOutAnimator?.cancel()
drawDwell = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
drawDwell = false
resetDwellAlpha()
}
@@ -182,13 +182,13 @@
invalidate()
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
retractDwellAnimator?.cancel()
dwellPulseOutAnimator?.cancel()
drawDwell = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
drawDwell = false
resetDwellAlpha()
}
@@ -239,14 +239,14 @@
expandDwellRippleAnimator
)
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
retractDwellAnimator?.cancel()
fadeDwellAnimator?.cancel()
visibility = VISIBLE
drawDwell = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
drawDwell = false
}
})
@@ -273,12 +273,12 @@
unlockedRippleAnimator = rippleAnimator.apply {
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
drawRipple = true
visibility = VISIBLE
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
onAnimationEnd?.run()
drawRipple = false
visibility = GONE
@@ -327,7 +327,7 @@
}
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
// To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader. (Twice bigger)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index b9fa240..a24a47b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -40,7 +40,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val faceAuthInteractor: KeyguardFaceAuthInteractor,
) : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
if (keyguardUpdateMonitor.shouldListenForFace()) {
val clickActionToRetryFace =
@@ -52,7 +52,7 @@
}
}
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
faceAuthInteractor.onAccessibilityAction()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 7f696fd..eb6864d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -119,7 +119,7 @@
private var overlayView: View? = null
set(value) {
field?.let { oldView ->
- val lottie = oldView.findViewById(R.id.sidefps_animation) as LottieAnimationView
+ val lottie = oldView.requireViewById(R.id.sidefps_animation) as LottieAnimationView
lottie.pauseAnimation()
windowManager.removeView(oldView)
orientationListener.disable()
@@ -274,7 +274,7 @@
}
overlayOffsets = offsets
- val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+ val lottie = view.requireViewById(R.id.sidefps_animation) as LottieAnimationView
view.rotation =
display.asSideFpsAnimationRotation(
offsets.isYAligned(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
index 8352d0a..5dafa61 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
@@ -38,7 +38,8 @@
override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
fun updateAccessibilityViewLocation(sensorBounds: Rect) {
- val fingerprintAccessibilityView: View = findViewById(R.id.udfps_enroll_accessibility_view)
+ val fingerprintAccessibilityView: View =
+ requireViewById(R.id.udfps_enroll_accessibility_view)
val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams
params.width = sensorBounds.width()
params.height = sensorBounds.height()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
index fb7b56e..8497879 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
@@ -33,7 +33,7 @@
@Main private val resources: Resources,
private val keyguardViewManager: StatusBarKeyguardViewManager,
) : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
val clickAction =
AccessibilityNodeInfo.AccessibilityAction(
@@ -43,7 +43,7 @@
info.addAction(clickAction)
}
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
// when an a11y service is enabled, double tapping on the fingerprint sensor should
// show the primary bouncer
return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index db30a55..e3fd3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -306,8 +306,9 @@
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
view.mUseExpandedOverlay = useExpandedOverlay
view.startIconAsyncInflate {
- (view.findViewById(R.id.udfps_animation_view_internal) as View).accessibilityDelegate =
- udfpsKeyguardAccessibilityDelegate
+ val animationViewInternal: View =
+ view.requireViewById(R.id.udfps_animation_view_internal)
+ animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index b538085..1ca57e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -60,6 +60,13 @@
return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
}
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency
+ * of TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds.
+ * (context: b/281765653#comment18)
+ * Using {@link View#announceForAccessibility} instead as workaround when sending events
+ * exceeding this frequency is required.
+ */
@JvmStatic
fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
if (!am.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 1f1a1b5..2a02667 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -89,7 +89,7 @@
)
val hat = gkResponse.gatekeeperHAT
lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
- emit(CredentialStatus.Success.Verified(hat))
+ emit(CredentialStatus.Success.Verified(checkNotNull(hat)))
} else if (response.timeout > 0) {
// if requests are being throttled, update the error message every
// second until the temporary lock has expired
@@ -226,8 +226,7 @@
is BiometricPromptRequest.Credential.Password ->
DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
}
- return devicePolicyManager.resources.getString(id) {
- // use fallback a string if not found
+ val getFallbackString = {
val defaultId =
when (request) {
is BiometricPromptRequest.Credential.Pin ->
@@ -239,6 +238,8 @@
}
getString(defaultId)
}
+
+ return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
}
private fun Context.getLastAttemptBeforeWipeUserMessage(
@@ -266,8 +267,8 @@
DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
else -> DevicePolicyResources.UNDEFINED
}
- return devicePolicyManager.resources.getString(id) {
- // use fallback a string if not found
+
+ val getFallbackString = {
val defaultId =
when (userType) {
UserType.PRIMARY ->
@@ -279,4 +280,6 @@
}
getString(defaultId)
}
+
+ return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index a3f34ce..b940ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -122,7 +122,7 @@
titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
titleView.marqueeRepeatLimit = -1
// select to enable marquee unless a screen reader is enabled
- titleView.isSelected = accessibilityManager.shouldMarquee()
+ titleView.isSelected = accessibilityManager?.shouldMarquee() ?: false
} else {
titleView.isSingleLine = false
titleView.ellipsize = null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 7b78761..490edc6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -25,6 +25,7 @@
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
+import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
@@ -45,7 +46,6 @@
import com.android.systemui.biometrics.AuthBiometricViewAdapter
import com.android.systemui.biometrics.AuthIconController
import com.android.systemui.biometrics.AuthPanelController
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -55,9 +55,13 @@
import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.VibratorHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@@ -78,20 +82,19 @@
backgroundView: View,
legacyCallback: Callback,
applicationScope: CoroutineScope,
+ vibratorHelper: VibratorHelper,
+ featureFlags: FeatureFlags,
): AuthBiometricViewAdapter {
val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
- fun notifyAccessibilityChanged() {
- Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
- }
val textColorError =
view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
val textColorHint =
view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
- val titleView = view.findViewById<TextView>(R.id.title)
- val subtitleView = view.findViewById<TextView>(R.id.subtitle)
- val descriptionView = view.findViewById<TextView>(R.id.description)
+ val titleView = view.requireViewById<TextView>(R.id.title)
+ val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
+ val descriptionView = view.requireViewById<TextView>(R.id.description)
// set selected to enable marquee unless a screen reader is enabled
titleView.isSelected =
@@ -100,18 +103,18 @@
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
descriptionView.movementMethod = ScrollingMovementMethod()
- val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
- val iconView = view.findViewById<LottieAnimationView>(R.id.biometric_icon)
- val indicatorMessageView = view.findViewById<TextView>(R.id.indicator)
+ val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+ val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
+ val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
// Negative-side (left) buttons
- val negativeButton = view.findViewById<Button>(R.id.button_negative)
- val cancelButton = view.findViewById<Button>(R.id.button_cancel)
- val credentialFallbackButton = view.findViewById<Button>(R.id.button_use_credential)
+ val negativeButton = view.requireViewById<Button>(R.id.button_negative)
+ val cancelButton = view.requireViewById<Button>(R.id.button_cancel)
+ val credentialFallbackButton = view.requireViewById<Button>(R.id.button_use_credential)
// Positive-side (right) buttons
- val confirmationButton = view.findViewById<Button>(R.id.button_confirm)
- val retryButton = view.findViewById<Button>(R.id.button_try_again)
+ val confirmationButton = view.requireViewById<Button>(R.id.button_confirm)
+ val retryButton = view.requireViewById<Button>(R.id.button_try_again)
// TODO(b/251476085): temporary workaround for the unsafe callbacks & legacy controllers
val adapter =
@@ -326,21 +329,14 @@
}
}
- // not sure why this is here, but the legacy code did it probably needed?
- launch {
- viewModel.isAuthenticating.collect { isAuthenticating ->
- if (isAuthenticating) {
- notifyAccessibilityChanged()
- }
- }
- }
-
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
// Disable background view for cancelling authentication once authenticated,
// and remove from talkback
if (authState.isAuthenticated) {
+ // Prevents Talkback from speaking subtitle after already authenticated
+ subtitleView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
backgroundView.setOnClickListener(null)
backgroundView.importantForAccessibility =
IMPORTANT_FOR_ACCESSIBILITY_NO
@@ -349,7 +345,6 @@
view.announceForAccessibility(
view.resources.getString(R.string.biometric_dialog_authenticated)
)
- notifyAccessibilityChanged()
launch {
delay(authState.delay)
@@ -381,7 +376,30 @@
!accessibilityManager.isEnabled ||
!accessibilityManager.isTouchExplorationEnabled
- notifyAccessibilityChanged()
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
+ * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
+ * b/281765653#comment18) Using {@link View#announceForAccessibility}
+ * instead as workaround since sending events exceeding this frequency is
+ * required.
+ */
+ indicatorMessageView?.text?.let {
+ if (it.isNotBlank()) {
+ view.announceForAccessibility(it)
+ }
+ }
+ }
+ }
+
+ // Play haptics
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ launch {
+ viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+ if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+ vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+ viewModel.clearHaptics()
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a286cf..370b36b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -72,7 +72,7 @@
}
}
- val iconHolderView = view.findViewById<View>(R.id.biometric_icon_frame)
+ val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
val fullSizeYOffset =
view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
@@ -205,7 +205,7 @@
}
private fun View.isLandscape(): Boolean {
- val r = context.display.rotation
+ val r = context.display?.rotation
return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index dca19c5..655e74a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,11 +17,14 @@
import android.hardware.biometrics.BiometricPrompt
import android.util.Log
+import android.view.HapticFeedbackConstants
import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
@@ -43,6 +46,7 @@
constructor(
private val interactor: PromptSelectorInteractor,
private val vibrator: VibratorHelper,
+ private val featureFlags: FeatureFlags,
) {
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
@@ -90,6 +94,11 @@
private val _forceLargeSize = MutableStateFlow(false)
private val _forceMediumSize = MutableStateFlow(false)
+ private val _hapticsToPlay = MutableStateFlow(HapticFeedbackConstants.NO_HAPTICS)
+
+ /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
+ val hapticsToPlay = _hapticsToPlay.asStateFlow()
+
/** The size of the prompt. */
val size: Flow<PromptSize> =
combine(
@@ -438,11 +447,26 @@
_forceLargeSize.value = true
}
- private fun VibratorHelper.success(modality: BiometricModality) =
- vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+ private fun VibratorHelper.success(modality: BiometricModality) {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
+ } else {
+ vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+ }
+ }
- private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
- vibrateAuthError("$TAG, modality = $modality BP::error")
+ private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ _hapticsToPlay.value = HapticFeedbackConstants.REJECT
+ } else {
+ vibrateAuthError("$TAG, modality = $modality BP::error")
+ }
+ }
+
+ /** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
+ fun clearHaptics() {
+ _hapticsToPlay.value = HapticFeedbackConstants.NO_HAPTICS
+ }
companion object {
private const val TAG = "PromptViewModel"
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 5ca36ab..ddb09749 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -156,9 +156,9 @@
}
windowLayoutParams.packageName = context.opPackageName
rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
- override fun onViewDetachedFromWindow(view: View?) {}
+ override fun onViewDetachedFromWindow(view: View) {}
- override fun onViewAttachedToWindow(view: View?) {
+ override fun onViewAttachedToWindow(view: View) {
layoutRipple()
rippleView.startRipple(Runnable {
windowManager.removeView(rippleView)
@@ -176,7 +176,7 @@
val height = bounds.height()
val maxDiameter = Integer.max(width, height) * 2f
rippleView.setMaxSize(maxDiameter, maxDiameter)
- when (context.display.rotation) {
+ when (context.display?.rotation) {
Surface.ROTATION_0 -> {
rippleView.setCenter(
width * normalizedPortPosX, height * normalizedPortPosY)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
index 63d57cc..a9f3b77 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -29,7 +29,7 @@
*/
class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
View.AccessibilityDelegate() {
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
if (action == ACTION_CLICK) {
falsingCollector.onA11yAction()
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 3e6ac86..c0d1951 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -100,7 +100,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
override fun getResolutionScale(): Float {
- context.display.getDisplayInfo(displayInfo.value)
+ context.display?.getDisplayInfo(displayInfo.value)
val maxDisplayMode =
displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
maxDisplayMode?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 2dd98dc..323070a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -22,6 +22,7 @@
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
+import com.android.systemui.shade.TouchLogger
import kotlin.math.pow
import kotlin.math.sqrt
import kotlinx.coroutines.DisposableHandle
@@ -83,6 +84,10 @@
interactionHandler.isLongPressHandlingEnabled = isEnabled
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
+ }
+
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
return interactionHandler.onTouchEvent(event?.toModel())
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
index f17d0f3..0b327a1 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
@@ -66,9 +66,9 @@
contrastButtons =
mapOf(
- CONTRAST_LEVEL_STANDARD to findViewById(R.id.contrast_button_standard),
- CONTRAST_LEVEL_MEDIUM to findViewById(R.id.contrast_button_medium),
- CONTRAST_LEVEL_HIGH to findViewById(R.id.contrast_button_high)
+ CONTRAST_LEVEL_STANDARD to requireViewById(R.id.contrast_button_standard),
+ CONTRAST_LEVEL_MEDIUM to requireViewById(R.id.contrast_button_medium),
+ CONTRAST_LEVEL_HIGH to requireViewById(R.id.contrast_button_high)
)
contrastButtons.forEach { (contrastLevel, contrastButton) ->
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index e8c97bf..4a534e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -190,7 +190,7 @@
PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
val servicePackageSet = serviceInfoSet.map { it.packageName }
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
- completedSeedingPackageSet.intersect(servicePackageSet)).apply()
+ completedSeedingPackageSet?.intersect(servicePackageSet) ?: emptySet()).apply()
var changed = false
favoriteComponentSet.subtract(serviceInfoSet).forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 23721c9..8bae667 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -193,7 +193,7 @@
ControlsAnimations.enterAnimation(pageIndicator).apply {
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
// Position the tooltip if necessary after animations are complete
// so we can get the position on screen. The tooltip is not
// rooted in the layout root.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index ff55b76d..a13f717 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -106,10 +106,8 @@
}
)
- getWindow().apply {
- setType(WINDOW_TYPE)
- setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
- }
+ window?.setType(WINDOW_TYPE)
+ window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
setOnShowListener(DialogInterface.OnShowListener { _ ->
val editText = requireViewById<EditText>(R.id.controls_pin_input)
editText.setHint(instructions)
@@ -153,9 +151,7 @@
)
}
return builder.create().apply {
- getWindow().apply {
- setType(WINDOW_TYPE)
- }
+ window?.setType(WINDOW_TYPE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index c04bc87..abe3423 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -384,7 +384,7 @@
)
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
stateAnimator = null
}
})
@@ -438,7 +438,7 @@
duration = 200L
interpolator = Interpolators.LINEAR
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
statusRowUpdater.invoke()
}
})
@@ -450,7 +450,7 @@
statusAnimator = AnimatorSet().apply {
playSequentially(fadeOut, fadeIn)
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
status.alpha = STATUS_ALPHA_ENABLED
statusAnimator = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index be50a14..98f17f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -132,8 +132,8 @@
init {
// To pass touches to the task inside TaskView.
- window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
- window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+ window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
+ window?.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
setContentView(R.layout.controls_detail_dialog)
@@ -182,7 +182,7 @@
}
// consume all insets to achieve slide under effect
- window.getDecorView().setOnApplyWindowInsetsListener {
+ checkNotNull(window).decorView.setOnApplyWindowInsetsListener {
v: View, insets: WindowInsets ->
val l = v.getPaddingLeft()
val r = v.getPaddingRight()
@@ -202,7 +202,7 @@
}
fun getTaskViewBounds(): Rect {
- val wm = context.getSystemService(WindowManager::class.java)
+ val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
val windowMetrics = wm.getCurrentWindowMetrics()
val rect = windowMetrics.bounds
val metricInsets = windowMetrics.windowInsets
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index ad2b785..dbbda9a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -67,7 +67,8 @@
iconMap.put(resourceId, icon)
}
}
- return RenderInfo(icon!!.constantState.newDrawable(context.resources), fg, bg)
+ return RenderInfo(
+ checkNotNull(icon?.constantState).newDrawable(context.resources), fg, bg)
}
fun registerComponentIcon(componentName: ComponentName, icon: Drawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index 84cda5a..3c2bfa0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -94,10 +94,8 @@
)
}
cvh.visibleDialog = builder.create().apply {
- getWindow().apply {
- setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
- show()
- }
+ window?.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+ show()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 1461135..b2c95a6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -244,7 +244,7 @@
cvh.clipLayer.level = it.animatedValue as Int
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
rangeAnimator = null
}
})
@@ -335,7 +335,7 @@
}
override fun onScroll(
- e1: MotionEvent,
+ e1: MotionEvent?,
e2: MotionEvent,
xDiff: Float,
yDiff: Float
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 4e62104..ac0d3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
@@ -47,6 +48,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
@@ -86,6 +88,7 @@
keyguardUpdateMonitor,
mainExecutor,
logger,
+ featureFlags,
)
)
}
@@ -110,6 +113,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -144,6 +148,7 @@
mainExecutor,
logger,
authController,
+ featureFlags
)
view.id = viewId
view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
index bcfeeb9e..cef45dc 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -51,7 +51,7 @@
val callback =
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
- context.display.getMetrics(displayMetricsHolder)
+ context.display?.getMetrics(displayMetricsHolder)
trySend(displayMetricsHolder)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index ae40f7e8..7150d69e 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -432,9 +432,11 @@
}
private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
+ Trace.beginSection(entry.name)
preamble(entry)
val dumpTime = measureTimeMillis(block)
footer(entry, dumpTime)
+ Trace.endSection()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
index f7e6b98..2e9d04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.dump
+import android.os.Trace
import java.io.PrintWriter
/**
@@ -83,31 +84,33 @@
) {
fun printTableData(pw: PrintWriter) {
+ Trace.beginSection("DumpsysTableLogger#printTableData")
printSectionStart(pw)
printSchema(pw)
printData(pw)
printSectionEnd(pw)
+ Trace.endSection()
}
private fun printSectionStart(pw: PrintWriter) {
- pw.println(HEADER_PREFIX + sectionName)
- pw.println("version $VERSION")
+ pw.append(HEADER_PREFIX).println(sectionName)
+ pw.append("version ").println(VERSION)
}
private fun printSectionEnd(pw: PrintWriter) {
- pw.println(FOOTER_PREFIX + sectionName)
+ pw.append(FOOTER_PREFIX).println(sectionName)
}
private fun printSchema(pw: PrintWriter) {
- pw.println(columns.joinToString(separator = SEPARATOR))
+ columns.joinTo(pw, separator = SEPARATOR).println()
}
private fun printData(pw: PrintWriter) {
val count = columns.size
- rows
- .filter { it.size == count }
- .forEach { dataLine ->
- pw.println(dataLine.joinToString(separator = SEPARATOR))
+ rows.forEach { dataLine ->
+ if (dataLine.size == count) {
+ dataLine.joinTo(pw, separator = SEPARATOR).println()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d1281f2..35a5d61 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -226,6 +226,12 @@
val WALLPAPER_PICKER_PAGE_TRANSITIONS =
unreleasedFlag("wallpaper_picker_page_transitions")
+ /** Add "Apply" button to wall paper picker's grid preview page. */
+ // TODO(b/294866904): Tracking bug.
+ @JvmField
+ val WALLPAPER_PICKER_GRID_APPLY_BUTTON =
+ unreleasedFlag("wallpaper_picker_grid_apply_button")
+
/** Whether to run the new udfps keyguard refactor code. */
// TODO(b/279440316): Tracking bug.
@JvmField
@@ -252,8 +258,7 @@
/** Whether to listen for fingerprint authentication over keyguard occluding activities. */
// TODO(b/283260512): Tracking bug.
- @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag("fp_listen_occluding_apps",
- teamfood = true)
+ @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
/** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
// TODO(b/286563884): Tracking bug
@@ -291,6 +296,12 @@
@JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
+ /** Flag to disable the face scanning animation pulsing. */
+ // TODO(b/295245791): Tracking bug.
+ @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
+ R.bool.flag_stop_pulsing_face_scanning_animation,
+ "stop_pulsing_face_scanning_animation")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
@@ -527,6 +538,12 @@
val ENABLE_PIP_APP_ICON_OVERLAY =
sysPropBooleanFlag("persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+
+ // TODO(b/293252410) : Tracking Bug
+ @JvmField
+ val LOCKSCREEN_ENABLE_LANDSCAPE =
+ unreleasedFlag("lockscreen.enable_landscape")
+
// TODO(b/273443374): Tracking Bug
@Keep
@JvmField
@@ -753,4 +770,8 @@
/** Enable the Compose implementation of the Quick Settings footer actions. */
@JvmField
val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions")
+
+ /** Enable the share wifi button in Quick Settings internet dialog. */
+ @JvmField
+ val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 7078341..b5b56b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -161,7 +161,7 @@
}
private fun updateIconTile() {
- val iconTile = rootView.findViewById(BACKLIGHT_ICON_ID) as ImageView
+ val iconTile = rootView.requireViewById(BACKLIGHT_ICON_ID) as ImageView
val backgroundDrawable = iconTile.background as ShapeDrawable
if (currentLevel == 0) {
iconTile.setColorFilter(dimmedIconColor)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index c019d21..5d7a3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -59,7 +59,7 @@
conflatedCallbackFlow {
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
@@ -71,7 +71,7 @@
)
}
- override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
trySendWithFailureLogging(
KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
@@ -133,13 +133,13 @@
return suspendCancellableCoroutine { continuation ->
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
continuation.resumeWith(
Result.success(response?.walletCards ?: emptyList())
)
}
- override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
continuation.resumeWith(Result.success(emptyList()))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 30f8f3e..ff5094e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -581,7 +581,7 @@
// We always want to invoke face detect in the main thread.
faceAuthLogger.faceDetectionStarted()
faceManager?.detectFace(
- detectCancellationSignal,
+ checkNotNull(detectCancellationSignal),
detectionCallback,
FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 6a2511f..1c0b73f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -163,12 +163,13 @@
private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
return with(point) {
+ val display = checkNotNull(context.display)
CircleReveal(
x,
y,
startRadius = 0,
endRadius =
- max(max(x, context.display.width - x), max(y, context.display.height - y)),
+ max(max(x, display.width - x), max(y, display.height - y)),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index ff8d5c9..3ec660a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -22,10 +22,14 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,6 +59,8 @@
@Application scope: CoroutineScope,
private val context: Context,
activityStarter: ActivityStarter,
+ powerInteractor: PowerInteractor,
+ featureFlags: FeatureFlags,
) {
private val keyguardOccludedByApp: Flow<Boolean> =
combine(
@@ -87,29 +93,37 @@
.ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
init {
- scope.launch {
- // On fingerprint success, go to the home screen
- fingerprintUnlockSuccessEvents.collect { goToHomeScreen() }
- }
+ if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+ scope.launch {
+ // On fingerprint success when the screen is on, go to the home screen
+ fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
+ if (it) {
+ goToHomeScreen()
+ }
+ // don't go to the home screen if the authentication is from AOD/dozing/off
+ }
+ }
- scope.launch {
- // On device fingerprint lockout, request the bouncer with a runnable to
- // go to the home screen. Without this, the bouncer won't proceed to the home screen.
- fingerprintLockoutEvents.collect {
- activityStarter.dismissKeyguardThenExecute(
- object : ActivityStarter.OnDismissAction {
- override fun onDismiss(): Boolean {
- goToHomeScreen()
- return false
- }
+ scope.launch {
+ // On device fingerprint lockout, request the bouncer with a runnable to
+ // go to the home screen. Without this, the bouncer won't proceed to the home
+ // screen.
+ fingerprintLockoutEvents.collect {
+ activityStarter.dismissKeyguardThenExecute(
+ object : ActivityStarter.OnDismissAction {
+ override fun onDismiss(): Boolean {
+ goToHomeScreen()
+ return false
+ }
- override fun willRunAnimationOnKeyguard(): Boolean {
- return false
- }
- },
- /* cancel= */ null,
- /* afterKeyguardGone */ false
- )
+ override fun willRunAnimationOnKeyguard(): Boolean {
+ return false
+ }
+ },
+ /* cancel= */ null,
+ /* afterKeyguardGone */ false
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
index d6883dd..5c072fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
@@ -67,14 +67,15 @@
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
keyguardRootViewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- ambientIndicationArea?.alpha = alpha
+ ambientIndicationArea?.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index a0a2abe..44acf4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -176,14 +176,15 @@
launch {
viewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- ambientIndicationArea?.alpha = alpha
+ ambientIndicationArea?.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
}
@@ -471,7 +472,7 @@
return true
}
- override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+ override fun onLongClickUseDefaultHapticFeedback(view: View) = false
}
@Deprecated("Deprecated as part of b/278057014")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index a385a0e..dc51944 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -73,25 +73,27 @@
launch {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
keyguardRootViewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- indicationArea.alpha = alpha
+ indicationArea.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
} else {
viewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
-
- indicationArea.alpha = alpha
+ indicationArea.apply {
+ this.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ this.alpha = alpha
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 63a6791..83b5463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -304,7 +304,7 @@
return true
}
- override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+ override fun onLongClickUseDefaultHapticFeedback(view: View) = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 162c109..82610e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -43,7 +43,7 @@
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
- val view = parentView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
+ val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
val disposableHandle =
view.repeatWhenAttached {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
index b568a9a..3bb01f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -42,13 +42,13 @@
view.accessibilityDelegate = viewModel.accessibilityDelegate
// bind child views
- UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+ UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
UdfpsFingerprintViewBinder.bind(
- view.findViewById(R.id.udfps_lockscreen_fp),
+ view.requireViewById(R.id.udfps_lockscreen_fp),
fingerprintViewModel
)
UdfpsBackgroundViewBinder.bind(
- view.findViewById(R.id.udfps_keyguard_fp_bg),
+ view.requireViewById(R.id.udfps_keyguard_fp_bg),
backgroundViewModel
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 58bc552..580db35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -117,7 +117,7 @@
private var host: SurfaceControlViewHost
val surfacePackage: SurfaceControlViewHost.SurfacePackage
- get() = host.surfacePackage
+ get() = checkNotNull(host.surfacePackage)
private lateinit var largeClockHostView: FrameLayout
private lateinit var smallClockHostView: FrameLayout
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 11e85d0..6d3b7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -26,6 +26,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@@ -51,15 +52,14 @@
)
/** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey =
- authenticationInteractor.canSwipeToDismiss
- .map { canSwipeToDismiss -> upDestinationSceneKey(canSwipeToDismiss) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- upDestinationSceneKey(authenticationInteractor.canSwipeToDismiss.value),
- )
+ val upDestinationSceneKey: Flow<SceneKey> =
+ authenticationInteractor.isUnlocked.map { isUnlocked ->
+ if (isUnlocked) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Bouncer
+ }
+ }
/** Notifies that the lock button on the lock screen was clicked. */
fun onLockButtonClicked() {
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index e064839..5f7991e 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -70,7 +70,7 @@
var lifecycleOwner: ViewLifecycleOwner? = null
val onAttachListener =
object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View?) {
+ override fun onViewAttachedToWindow(v: View) {
Assert.isMainThread()
lifecycleOwner?.onDestroy()
lifecycleOwner =
@@ -81,7 +81,7 @@
)
}
- override fun onViewDetachedFromWindow(v: View?) {
+ override fun onViewDetachedFromWindow(v: View) {
lifecycleOwner?.onDestroy()
lifecycleOwner = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index cc1504a..b6577f7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,14 @@
return factory.create("ShadeLog", 500, false);
}
+ /** Provides a logging buffer for Shade messages. */
+ @Provides
+ @SysUISingleton
+ @ShadeTouchLog
+ public static LogBuffer provideShadeTouchLogBuffer(LogBufferFactory factory) {
+ return factory.create("ShadeTouchLog", 500, false);
+ }
+
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
new file mode 100644
index 0000000..b13667e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for tracking touches in various shade child views. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeTouchLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 67a985e..a7ffc5f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -302,14 +302,14 @@
@Synchronized
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(HEADER_PREFIX + name)
- pw.println("version $VERSION")
+ pw.append(HEADER_PREFIX).println(name)
+ pw.append("version ").println(VERSION)
lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
for (i in 0 until buffer.size) {
buffer[i].dump(pw)
}
- pw.println(FOOTER_PREFIX + name)
+ pw.append(FOOTER_PREFIX).println(name)
}
/** Dumps an individual [TableChange]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 35f5a8c..a91917a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -514,7 +514,7 @@
* Returns true when the down event of the scroll hits within the target box of the thumb.
*/
override fun onScroll(
- eventStart: MotionEvent,
+ eventStart: MotionEvent?,
event: MotionEvent,
distanceX: Float,
distanceY: Float
@@ -528,7 +528,7 @@
* Gestures that include a fling are considered a false gesture on the seek bar.
*/
override fun onFling(
- eventStart: MotionEvent,
+ eventStart: MotionEvent?,
event: MotionEvent,
velocityX: Float,
velocityY: Float
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 207df6b..a1291a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -149,11 +149,7 @@
// Check if smartspace has explicitly specified whether to re-activate resumable media.
// The default behavior is to trigger if the smartspace data is active.
val shouldTriggerResume =
- if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) {
- data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true)
- } else {
- true
- }
+ data.cardAction?.extras?.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) ?: true
val shouldReactivate =
shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive
@@ -269,9 +265,7 @@
"Cannot create dismiss action click action: extras missing dismiss_intent."
)
} else if (
- dismissIntent.getComponent() != null &&
- dismissIntent.getComponent().getClassName() ==
- EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
+ dismissIntent.component?.className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
) {
// Dismiss the card Smartspace data through Smartspace trampoline activity.
context.startActivity(dismissIntent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 576eb9e..282a65a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -22,6 +22,7 @@
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
import android.app.StatusBarManager
+import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
@@ -1623,20 +1624,18 @@
* SmartspaceTarget's data is invalid.
*/
private fun toSmartspaceMediaData(target: SmartspaceTarget): SmartspaceMediaData {
- var dismissIntent: Intent? = null
- if (target.baseAction != null && target.baseAction.extras != null) {
- dismissIntent =
- target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
- as Intent?
- }
+ val baseAction: SmartspaceAction? = target.baseAction
+ val dismissIntent =
+ baseAction?.extras?.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
val isActive =
when {
!mediaFlags.isPersistentSsCardEnabled() -> true
- target.baseAction == null -> true
- else ->
- target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
- EXTRA_VALUE_TRIGGER_PERIODIC
+ baseAction == null -> true
+ else -> {
+ val triggerSource = baseAction.extras?.getString(EXTRA_KEY_TRIGGER_SOURCE)
+ triggerSource != EXTRA_VALUE_TRIGGER_PERIODIC
+ }
}
packageName(target)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index d6f941d..6a8ffb7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -65,7 +65,7 @@
private val sessionListener =
object : MediaSessionManager.OnActiveSessionsChangedListener {
- override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+ override fun onActiveSessionsChanged(controllers: List<MediaController>?) {
handleControllersChanged(controllers)
}
}
@@ -190,16 +190,18 @@
}
}
- private fun handleControllersChanged(controllers: List<MediaController>) {
+ private fun handleControllersChanged(controllers: List<MediaController>?) {
packageControllers.clear()
- controllers.forEach { controller ->
+ controllers?.forEach { controller ->
packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) }
?: run {
val tokens = mutableListOf(controller)
packageControllers.put(controller.packageName, tokens)
}
}
- tokensWithNotifications.retainAll(controllers.map { TokenId(it.sessionToken) })
+ controllers?.map { TokenId(it.sessionToken) }?.let {
+ tokensWithNotifications.retainAll(it)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index b46ebb2..b9cc772 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -195,7 +195,7 @@
}
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
backgroundAnimation = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 937a618..646d1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -98,11 +98,11 @@
addListener(
object : AnimatorListenerAdapter() {
var cancelled = false
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
cancelled = true
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
if (cancelled) {
return
}
@@ -226,7 +226,7 @@
)
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
rippleData.progress = 0f
rippleAnimation = null
invalidateSelf()
@@ -270,11 +270,8 @@
return bounds
}
- override fun onStateChange(stateSet: IntArray?): Boolean {
+ override fun onStateChange(stateSet: IntArray): Boolean {
val changed = super.onStateChange(stateSet)
- if (stateSet == null) {
- return changed
- }
val wasPressed = pressed
var enabled = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 1ace316..ce50a11 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -127,19 +127,19 @@
object : GestureDetector.SimpleOnGestureListener() {
override fun onFling(
eStart: MotionEvent?,
- eCurrent: MotionEvent?,
+ eCurrent: MotionEvent,
vX: Float,
vY: Float
) = onFling(vX, vY)
override fun onScroll(
down: MotionEvent?,
- lastMotion: MotionEvent?,
+ lastMotion: MotionEvent,
distanceX: Float,
distanceY: Float
- ) = onScroll(down!!, lastMotion!!, distanceX)
+ ) = onScroll(down!!, lastMotion, distanceX)
- override fun onDown(e: MotionEvent?): Boolean {
+ override fun onDown(e: MotionEvent): Boolean {
if (falsingProtectionNeeded) {
falsingCollector.onNotificationStartDismissing()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index fe8ebaf..c1c757e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -180,20 +180,20 @@
object : AnimatorListenerAdapter() {
private var cancelled: Boolean = false
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
cancelled = true
animationPending = false
rootView?.removeCallbacks(startAnimation)
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
isCrossFadeAnimatorRunning = false
if (!cancelled) {
applyTargetStateIfNotAnimating()
}
}
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
cancelled = false
animationPending = false
}
@@ -606,7 +606,7 @@
val viewHost = UniqueObjectHostView(context)
viewHost.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(p0: View?) {
+ override fun onViewAttachedToWindow(p0: View) {
if (rootOverlay == null) {
rootView = viewHost.viewRootImpl.view
rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
@@ -614,7 +614,7 @@
viewHost.removeOnAttachStateChangeListener(this)
}
- override fun onViewDetachedFromWindow(p0: View?) {}
+ override fun onViewDetachedFromWindow(p0: View) {}
}
)
return viewHost
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index be570b4..631a0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -144,12 +144,12 @@
setListeningToMediaData(true)
hostView.addOnAttachStateChangeListener(
object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View?) {
+ override fun onViewAttachedToWindow(v: View) {
setListeningToMediaData(true)
updateViewVisibility()
}
- override fun onViewDetachedFromWindow(v: View?) {
+ override fun onViewDetachedFromWindow(v: View) {
setListeningToMediaData(false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 583c626..16dfc21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -117,7 +117,7 @@
}
addListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
heightAnimator = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index bbd3d33..da8e106 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -201,13 +201,13 @@
}
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
- val packageName = newInfo.routeInfo.clientPackageName
+ val packageName: String? = newInfo.routeInfo.clientPackageName
var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context,
packageName,
isReceiver = true,
) {
- logger.logPackageNotFound(packageName)
+ packageName?.let { logger.logPackageNotFound(it) }
}
if (newInfo.appNameOverride != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
index 5013802..fbf7e25 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
@@ -68,9 +68,9 @@
)
rippleView.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
- override fun onViewDetachedFromWindow(view: View?) {}
+ override fun onViewDetachedFromWindow(view: View) {}
- override fun onViewAttachedToWindow(view: View?) {
+ override fun onViewAttachedToWindow(view: View) {
if (view == null) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 0b0535d..35018f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -54,7 +54,7 @@
// Reset all listeners to animator.
animator.removeAllListeners()
animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
onAnimationEnd?.run()
isStarted = false
}
@@ -86,7 +86,7 @@
invalidate()
}
animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
animation?.let { visibility = GONE }
onAnimationEnd?.run()
isStarted = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index f75f8b9..87d0098 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -162,7 +162,7 @@
logger: MediaTttSenderLogger,
instanceId: InstanceId,
): ChipbarInfo {
- val packageName = routeInfo.clientPackageName
+ val packageName = checkNotNull(routeInfo.clientPackageName)
val otherDeviceName =
if (routeInfo.name.isBlank()) {
context.getString(R.string.media_ttt_default_device_type)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index c816446..64de9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -88,7 +88,7 @@
.inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
as ViewGroup
- val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+ val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
container.setTaskHeightSize()
val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
index 38d4e69..6480a47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -81,8 +81,8 @@
return MediaProjectionState.EntireScreen
}
val matchingTask =
- tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
- ?: return MediaProjectionState.EntireScreen
+ tasksRepository.findRunningTaskFromWindowContainerToken(
+ checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen
return MediaProjectionState.SingleTask(matchingTask)
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 4d30634..63d4634 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -77,8 +77,13 @@
.build()
}
- private fun PackageManager.getApplicationLabel(packageName: String?): String? =
- runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+ private fun PackageManager.getApplicationLabel(packageName: String?): String? {
+ if (packageName == null) {
+ return null
+ }
+
+ return runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
.getOrNull()
?.let { info -> getApplicationLabel(info).toString() }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index 5f338c3..46c8d35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -132,13 +132,13 @@
LayoutInflater.from(context)
.inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
- noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener {
+ noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener {
onGotItClicked()
}
// The Tile preview has colorBackground as its background. Change it so it's different than
// the activity's background.
- val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+ val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background)
val shape = item.background as GradientDrawable
val ta =
context.theme.obtainStyledAttributes(
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f4aa27d..c202f14 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -192,7 +192,7 @@
return null
}
val closeAppButton =
- window.layoutInflater.inflate(
+ checkNotNull(window).layoutInflater.inflate(
R.layout.privacy_dialog_card_button,
expandedLayout,
false
@@ -248,7 +248,7 @@
private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
val manageButton =
- window.layoutInflater.inflate(
+ checkNotNull(window).layoutInflater.inflate(
R.layout.privacy_dialog_card_button,
expandedLayout,
false
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1afc885..d2eac45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,12 +23,14 @@
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
import java.io.PrintWriter;
@@ -129,6 +131,11 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("QS", ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateExpansion();
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index cf7abdd..76d9b03 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -17,21 +17,22 @@
package com.android.systemui.scene.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
/**
* Generic business logic and app state accessors for the scene framework.
@@ -44,6 +45,7 @@
class SceneInteractor
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val repository: SceneContainerRepository,
private val logger: SceneLogger,
) {
@@ -88,6 +90,22 @@
*/
val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
+ /**
+ * The key of the scene that the UI is currently transitioning to or `null` if there is no
+ * active transition at the moment.
+ *
+ * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers
+ * like Java code.
+ */
+ val transitioningTo: StateFlow<SceneKey?> =
+ transitionState
+ .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
/** Whether the scene container is visible. */
val isVisible: StateFlow<Boolean> = repository.isVisible
@@ -142,21 +160,6 @@
repository.setTransitionState(transitionState)
}
- /**
- * Returns a stream of events that emits one [Unit] every time the framework transitions from
- * [from] to [to].
- */
- fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> {
- return transitionState
- .mapNotNull { it as? ObservableTransitionState.Idle }
- .map { idleState -> idleState.scene }
- .distinctUntilChanged()
- .pairwise()
- .mapNotNull { (previousSceneKey, currentSceneKey) ->
- Unit.takeIf { previousSceneKey == from && currentSceneKey == to }
- }
- }
-
/** Handles a remote user input. */
fun onRemoteUserInput(input: RemoteUserInput) {
_remoteUserInput.value = input
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index afefccb..1747099 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -178,12 +178,24 @@
}
WakefulnessState.STARTING_TO_WAKE -> {
val authMethod = authenticationInteractor.getAuthenticationMethod()
- if (authMethod == AuthenticationMethodModel.None) {
- switchToScene(
- targetSceneKey = SceneKey.Gone,
- loggingReason =
- "device is starting to wake up while auth method is None",
- )
+ val isUnlocked = authenticationInteractor.isUnlocked.value
+ when {
+ authMethod == AuthenticationMethodModel.None -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason =
+ "device is starting to wake up while auth method is" +
+ " none",
+ )
+ }
+ authMethod.isSecure && isUnlocked -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason =
+ "device is starting to wake up while unlocked with a" +
+ " secure auth method",
+ )
+ }
}
}
else -> Unit
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index b340043..23894a3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -50,22 +50,20 @@
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- window.apply {
- addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
- setGravity(Gravity.CENTER)
- }
+ window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ window?.setGravity(Gravity.CENTER)
setContentView(R.layout.screen_share_dialog)
- dialogTitle = findViewById(R.id.screen_share_dialog_title)
- warning = findViewById(R.id.text_warning)
- startButton = findViewById(android.R.id.button1)
- cancelButton = findViewById(android.R.id.button2)
+ dialogTitle = requireViewById(R.id.screen_share_dialog_title)
+ warning = requireViewById(R.id.text_warning)
+ startButton = requireViewById(android.R.id.button1)
+ cancelButton = requireViewById(android.R.id.button2)
updateIcon()
initScreenShareOptions()
createOptionsView(getOptionsViewLayoutId())
}
private fun updateIcon() {
- val icon = findViewById<ImageView>(R.id.screen_share_dialog_icon)
+ val icon = requireViewById<ImageView>(R.id.screen_share_dialog_icon)
if (dialogIconTint != null) {
icon.setColorFilter(context.getColor(dialogIconTint))
}
@@ -92,7 +90,7 @@
options
)
adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
- screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
+ screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner)
screenShareModeSpinner.adapter = adapter
screenShareModeSpinner.onItemSelectedListener = this
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 604d449..e8683fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -100,11 +100,11 @@
@LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
private fun initRecordOptionsView() {
- audioSwitch = findViewById(R.id.screenrecord_audio_switch)
- tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
- tapsView = findViewById(R.id.show_taps)
+ audioSwitch = requireViewById(R.id.screenrecord_audio_switch)
+ tapsSwitch = requireViewById(R.id.screenrecord_taps_switch)
+ tapsView = requireViewById(R.id.show_taps)
updateTapsViewVisibility()
- options = findViewById(R.id.screen_recording_options)
+ options = requireViewById(R.id.screen_recording_options)
val a: ArrayAdapter<*> =
ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index fc89a9e..f4d19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
@@ -38,6 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
import java.util.concurrent.Executor;
@@ -59,6 +61,7 @@
private float mViewAlpha = 1.0f;
private Drawable mDrawable;
private PorterDuffColorFilter mColorFilter;
+ private String mScrimName;
private int mTintColor;
private boolean mBlendWithMainColor = true;
private Runnable mChangeRunnable;
@@ -336,6 +339,15 @@
}
}
+ public void setScrimName(String scrimName) {
+ mScrimName = scrimName;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(mScrimName, ev, super.dispatchTouchEvent(ev));
+ }
+
/**
* The position of the bottom of the scrim, used for clipping.
* @see #enableBottomEdgeConcave(boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 6143308..4644d41 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -108,7 +108,7 @@
* @see NPVCDownEventState.asStringList
*/
fun toList(): List<Row> {
- return buffer.asSequence().map { it.asStringList }.toList()
+ return buffer.map { it.asStringList }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index af3cc86..c501d88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -106,6 +106,11 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("NPV", ev, super.dispatchTouchEvent(ev));
+ }
+
+ @Override
public void dispatchConfigurationChanged(Configuration newConfig) {
super.dispatchConfigurationChanged(newConfig);
mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 35fd98c..d0cbb1b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1117,7 +1117,8 @@
collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
mDreamingToLockscreenTransition, mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
- setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+ setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
mDreamingToLockscreenTransitionTranslationY),
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1153,7 +1154,8 @@
collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition, mMainDispatcher);
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
- setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+ setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
mLockscreenToDreamingTransitionTranslationY),
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -3447,11 +3449,13 @@
ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
+ Trace.beginSection("Table<DownEvents>");
new DumpsysTableLogger(
TAG,
NPVCDownEventState.TABLE_HEADERS,
mLastDownEvents.toList()
).printTableData(ipw);
+ Trace.endSection();
}
@Override
@@ -4740,6 +4744,16 @@
mCurrentPanelState = state;
}
+ private Consumer<Float> setDreamLockscreenTransitionAlpha(
+ NotificationStackScrollLayoutController stackScroller) {
+ return (Float alpha) -> {
+ // Also animate the status bar's alpha during transitions between the lockscreen and
+ // dreams.
+ mKeyguardStatusBarViewController.setAlpha(alpha);
+ setTransitionAlpha(stackScroller).accept(alpha);
+ };
+ }
+
private Consumer<Float> setTransitionAlpha(
NotificationStackScrollLayoutController stackScroller) {
return (Float alpha) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 1f401fb..d2b62eb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -840,13 +840,17 @@
pw.println(" mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
pw.println(mCurrentState);
if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
+ Trace.beginSection("mWindowRootView.dump()");
mWindowRootView.getViewRootImpl().dump(" ", pw);
+ Trace.endSection();
}
+ Trace.beginSection("Table<State>");
new DumpsysTableLogger(
TAG,
NotificationShadeWindowState.TABLE_HEADERS,
mStateBuffer.toList()
).printTableData(pw);
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index d252943..e3010ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -170,7 +170,7 @@
* @see [NotificationShadeWindowState.asStringList]
*/
fun toList(): List<Row> {
- return buffer.asSequence().map { it.asStringList }.toList()
+ return buffer.map { it.asStringList }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index a9c4aeb..f9b4e67 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -107,6 +107,8 @@
result = result != null ? result : super.dispatchTouchEvent(ev);
+ TouchLogger.logDispatchTouch(TAG, ev, result);
+
mInteractionEventHandler.dispatchTouchEventComplete();
return result;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5b5785e..832a25b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -91,6 +91,7 @@
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final LockIconViewController mLockIconViewController;
+ private final ShadeLogger mShadeLogger;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -151,6 +152,7 @@
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
+ ShadeLogger shadeLogger,
PulsingGestureListener pulsingGestureListener,
LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
@@ -176,6 +178,7 @@
mStatusBarWindowStateController = statusBarWindowStateController;
mLockIconViewController = lockIconViewController;
mBackActionInteractor = backActionInteractor;
+ mShadeLogger = shadeLogger;
mLockIconViewController.init();
mService = centralSurfaces;
mPowerInteractor = powerInteractor;
@@ -223,6 +226,13 @@
return mView.findViewById(R.id.keyguard_message_area);
}
+ private Boolean logDownDispatch(MotionEvent ev, String msg, Boolean result) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.logShadeWindowDispatch(ev, msg, result);
+ }
+ return result;
+ }
+
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -237,8 +247,8 @@
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
if (mStatusBarViewController == null) { // Fix for b/192490822
- Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
- return false;
+ return logDownDispatch(ev,
+ "Ignoring touch while statusBarView not yet set", false);
}
boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
@@ -250,10 +260,9 @@
}
// Reset manual touch dispatch state here but make sure the UP/CANCEL event still
- // gets
- // delivered.
+ // gets delivered.
if (!isCancel && mService.shouldIgnoreTouch()) {
- return false;
+ return logDownDispatch(ev, "touch ignored by CS", false);
}
if (isDown) {
@@ -265,8 +274,11 @@
mTouchActive = false;
mDownEvent = null;
}
- if (mTouchCancelled || mExpandAnimationRunning) {
- return false;
+ if (mTouchCancelled) {
+ return logDownDispatch(ev, "touch cancelled", false);
+ }
+ if (mExpandAnimationRunning) {
+ return logDownDispatch(ev, "expand animation running", false);
}
if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -280,17 +292,17 @@
}
if (mIsOcclusionTransitionRunning) {
- return false;
+ return logDownDispatch(ev, "occlusion transition running", false);
}
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
if (mDreamingWakeupGestureHandler != null
&& mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
- return true;
+ return logDownDispatch(ev, "dream wakeup gesture handled", true);
}
if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
- return true;
+ return logDownDispatch(ev, "dispatched to Keyguard", true);
}
if (mBrightnessMirror != null
&& mBrightnessMirror.getVisibility() == View.VISIBLE) {
@@ -298,7 +310,7 @@
// you can't touch anything other than the brightness slider while the mirror is
// showing and the rest of the panel is transparent.
if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- return false;
+ return logDownDispatch(ev, "disallowed new pointer", false);
}
}
if (isDown) {
@@ -329,7 +341,9 @@
expandingBelowNotch = true;
}
if (expandingBelowNotch) {
- return mStatusBarViewController.sendTouchToView(ev);
+ return logDownDispatch(ev,
+ "expand below notch. sending touch to status bar",
+ mStatusBarViewController.sendTouchToView(ev));
}
if (!mIsTrackingBarGesture && isDown
@@ -339,9 +353,10 @@
if (mStatusBarViewController.touchIsWithinView(x, y)) {
if (mStatusBarWindowStateController.windowIsShowing()) {
mIsTrackingBarGesture = true;
- return mStatusBarViewController.sendTouchToView(ev);
- } else { // it's hidden or hiding, don't send to notification shade.
- return true;
+ return logDownDispatch(ev, "sending touch to status bar",
+ mStatusBarViewController.sendTouchToView(ev));
+ } else {
+ return logDownDispatch(ev, "hidden or hiding", true);
}
}
} else if (mIsTrackingBarGesture) {
@@ -349,10 +364,10 @@
if (isUp || isCancel) {
mIsTrackingBarGesture = false;
}
- return sendToStatusBar;
+ return logDownDispatch(ev, "sending bar gesture to status bar",
+ sendToStatusBar);
}
-
- return null;
+ return logDownDispatch(ev, "no custom touch dispatch of down event", null);
}
@Override
@@ -364,18 +379,26 @@
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
if (mStatusBarStateController.isDozing() && !mService.isPulsing()
&& !mDockManager.isDocked()) {
- // Capture all touch events in always-on.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: capture all touch events in always-on");
+ }
return true;
}
if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
// Don't allow touches to proceed to underlying views if alternate
// bouncer is showing
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: alt bouncer showing");
+ }
return true;
}
if (mLockIconViewController.onInterceptTouchEvent(ev)) {
// immediately return true; don't send the touch to the drag down helper
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: don't send touch to drag down helper");
+ }
return true;
}
@@ -383,7 +406,13 @@
&& mDragDownHelper.isDragDownEnabled()
&& !mService.isBouncerShowing()
&& !mStatusBarStateController.isDozing()) {
- return mDragDownHelper.onInterceptTouchEvent(ev);
+ boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
+ if (result) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: drag down helper intercepted");
+ }
+ }
+ return result;
} else {
return false;
}
@@ -495,6 +524,7 @@
}
public void cancelCurrentTouch() {
+ mShadeLogger.d("NSWVC: cancelling current touch");
if (mTouchActive) {
final long now = mClock.uptimeMillis();
final MotionEvent event;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 3b3df50..a4e439b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -22,6 +22,7 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowInsets;
@@ -183,6 +184,12 @@
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
+ super.dispatchTouchEvent(ev));
+ }
+
+ @Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (mIsMigratingNSSL) {
return super.drawChild(canvas, child, drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 22c63817..d7a3392 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -27,6 +27,8 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.ShadeTouchLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -56,6 +58,7 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
+ private final LogBuffer mTouchLog;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarStateController mStatusBarStateController;
@@ -79,6 +82,7 @@
public ShadeControllerImpl(
CommandQueue commandQueue,
@Main Executor mainExecutor,
+ @ShadeTouchLog LogBuffer touchLog,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -92,6 +96,7 @@
) {
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
+ mTouchLog = touchLog;
mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
mStatusBarWindowController = statusBarWindowController;
@@ -413,6 +418,7 @@
@Override
public void start() {
+ TouchLogger.logTouchesTo(mTouchLog);
getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
getShadeViewController().setOpenCloseListener(
new OpenCloseListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index c6cb9c4..bea12de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -130,12 +130,12 @@
private lateinit var carrierIconSlots: List<String>
private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
- private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
- private val clock: Clock = header.findViewById(R.id.clock)
- private val date: TextView = header.findViewById(R.id.date)
- private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
- private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
- private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons)
+ private val batteryIcon: BatteryMeterView = header.requireViewById(R.id.batteryRemainingIcon)
+ private val clock: Clock = header.requireViewById(R.id.clock)
+ private val date: TextView = header.requireViewById(R.id.date)
+ private val iconContainer: StatusIconContainer = header.requireViewById(R.id.statusIcons)
+ private val mShadeCarrierGroup: ShadeCarrierGroup = header.requireViewById(R.id.carrier_group)
+ private val systemIcons: View = header.requireViewById(R.id.shade_header_system_icons)
private var roundedCorners = 0
private var cutout: DisplayCutout? = null
@@ -582,7 +582,7 @@
inner class CustomizerAnimationListener(
private val enteringCustomizing: Boolean,
) : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
header.animate().setListener(null)
if (enteringCustomizing) {
@@ -590,7 +590,7 @@
}
}
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
super.onAnimationStart(animation)
if (!enteringCustomizing) {
customizing = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 1c30bdd..c3ef925 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -79,19 +79,39 @@
fun logMotionEvent(event: MotionEvent, message: String) {
buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = message
- long1 = event.eventTime
- long2 = event.downTime
- int1 = event.action
- int2 = event.classification
- double1 = event.y.toDouble()
- },
- {
- "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
- }
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ },
+ {
+ "$str1: eventTime=$long1,downTime=$long2,action=$int1,class=$int2"
+ }
+ )
+ }
+
+ /** Logs motion event dispatch results from NotificationShadeWindowViewController. */
+ fun logShadeWindowDispatch(event: MotionEvent, message: String, result: Boolean?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ },
+ {
+ val prefix = when (result) {
+ true -> "SHADE TOUCH REROUTED"
+ false -> "SHADE TOUCH BLOCKED"
+ null -> "SHADE TOUCH DISPATCHED"
+ }
+ "$prefix: eventTime=$long1,downTime=$long2, reason=$str1"
+ }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 6e76784..05b1ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -106,7 +106,7 @@
featureFlags: FeatureFlags,
): NotificationShadeWindowView {
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- return root.findViewById(R.id.legacy_window_root)
+ return root.requireViewById(R.id.legacy_window_root)
}
return root as NotificationShadeWindowView?
?: throw IllegalStateException("root view not a NotificationShadeWindowView")
@@ -118,7 +118,7 @@
fun providesNotificationStackScrollLayout(
notificationShadeWindowView: NotificationShadeWindowView,
): NotificationStackScrollLayout {
- return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
+ return notificationShadeWindowView.requireViewById(R.id.notification_stack_scroller)
}
@Provides
@@ -153,7 +153,7 @@
fun providesNotificationPanelView(
notificationShadeWindowView: NotificationShadeWindowView,
): NotificationPanelView {
- return notificationShadeWindowView.findViewById(R.id.notification_panel)
+ return notificationShadeWindowView.requireViewById(R.id.notification_panel)
}
/**
@@ -175,7 +175,7 @@
fun providesLightRevealScrim(
notificationShadeWindowView: NotificationShadeWindowView,
): LightRevealScrim {
- return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
+ return notificationShadeWindowView.requireViewById(R.id.light_reveal_scrim)
}
@Provides
@@ -183,7 +183,7 @@
fun providesKeyguardRootView(
notificationShadeWindowView: NotificationShadeWindowView,
): KeyguardRootView {
- return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
+ return notificationShadeWindowView.requireViewById(R.id.keyguard_root_view)
}
@Provides
@@ -191,7 +191,7 @@
fun providesSharedNotificationContainer(
notificationShadeWindowView: NotificationShadeWindowView,
): SharedNotificationContainer {
- return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+ return notificationShadeWindowView.requireViewById(R.id.shared_notification_container)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -200,7 +200,7 @@
fun providesAuthRippleView(
notificationShadeWindowView: NotificationShadeWindowView,
): AuthRippleView? {
- return notificationShadeWindowView.findViewById(R.id.auth_ripple)
+ return notificationShadeWindowView.requireViewById(R.id.auth_ripple)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -212,9 +212,9 @@
featureFlags: FeatureFlags
): LockIconView {
if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
- return keyguardRootView.findViewById(R.id.lock_icon_view)
+ return keyguardRootView.requireViewById(R.id.lock_icon_view)
} else {
- return notificationPanelView.findViewById(R.id.lock_icon_view)
+ return notificationPanelView.requireViewById(R.id.lock_icon_view)
}
}
@@ -224,7 +224,7 @@
fun providesTapAgainView(
notificationPanelView: NotificationPanelView,
): TapAgainView {
- return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
+ return notificationPanelView.requireViewById(R.id.shade_falsing_tap_again)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -233,7 +233,7 @@
fun providesNotificationsQuickSettingsContainer(
notificationShadeWindowView: NotificationShadeWindowView,
): NotificationsQuickSettingsContainer {
- return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
+ return notificationShadeWindowView.requireViewById(R.id.notification_container_parent)
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -243,7 +243,7 @@
fun providesShadeHeaderView(
notificationShadeWindowView: NotificationShadeWindowView,
): MotionLayout {
- val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
+ val stub = notificationShadeWindowView.requireViewById<ViewStub>(R.id.qs_header_stub)
val layoutId = R.layout.combined_qs_header
stub.layoutResource = layoutId
return stub.inflate() as MotionLayout
@@ -260,7 +260,7 @@
@SysUISingleton
@Named(SHADE_HEADER)
fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
- return view.findViewById(R.id.batteryRemainingIcon)
+ return view.requireViewById(R.id.batteryRemainingIcon)
}
@Provides
@@ -295,7 +295,7 @@
fun providesOngoingPrivacyChip(
@Named(SHADE_HEADER) header: MotionLayout,
): OngoingPrivacyChip {
- return header.findViewById(R.id.privacy_chip)
+ return header.requireViewById(R.id.privacy_chip)
}
@Provides
@@ -304,7 +304,7 @@
fun providesStatusIconContainer(
@Named(SHADE_HEADER) header: MotionLayout,
): StatusIconContainer {
- return header.findViewById(R.id.statusIcons)
+ return header.requireViewById(R.id.statusIcons)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
new file mode 100644
index 0000000..d96dc12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+
+private const val TAG = "systemui.shade.touch"
+
+/**
+ * A logger for tracking touch dispatching in the shade view hierarchy. The purpose of this logger
+ * is to passively observe dispatchTouchEvent calls in order to see which subtrees of the shade are
+ * handling touches. Additionally, some touches may be passively observed for views near the top of
+ * the shade hierarchy that cannot intercept touches, i.e. scrims. The usage of static methods for
+ * logging is sub-optimal in many ways, but it was selected in this case to make usage of this
+ * non-function diagnostic code as low friction as possible.
+ */
+class TouchLogger {
+ companion object {
+ private var touchLogger: DispatchTouchLogger? = null
+
+ @JvmStatic
+ fun logTouchesTo(buffer: LogBuffer) {
+ touchLogger = DispatchTouchLogger(buffer)
+ }
+
+ @JvmStatic
+ fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean): Boolean {
+ touchLogger?.logDispatchTouch(viewTag, ev, result)
+ return result
+ }
+ }
+}
+
+/** Logs touches. */
+private class DispatchTouchLogger(private val buffer: LogBuffer) {
+ fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean) {
+ // NOTE: never log position of touches for security purposes
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = viewTag
+ int1 = ev.action
+ long1 = ev.downTime
+ bool1 = result
+ },
+ { "Touch: view=$str1, type=${typeToString(int1)}, downtime=$long1, result=$bool1" }
+ )
+ }
+
+ private fun typeToString(type: Int): String {
+ return when (type) {
+ MotionEvent.ACTION_DOWN -> "DOWN"
+ MotionEvent.ACTION_UP -> "UP"
+ MotionEvent.ACTION_MOVE -> "MOVE"
+ MotionEvent.ACTION_CANCEL -> "CANCEL"
+ MotionEvent.ACTION_POINTER_DOWN -> "POINTER_DOWN"
+ MotionEvent.ACTION_POINTER_UP -> "POINTER_UP"
+ else -> "OTHER"
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 87abc92..8edc26d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -25,7 +25,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the shade scene. */
@@ -39,14 +39,22 @@
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
- authenticationInteractor.isUnlocked
- .map { isUnlocked -> upDestinationSceneKey(isUnlocked = isUnlocked) }
+ combine(
+ authenticationInteractor.isUnlocked,
+ authenticationInteractor.canSwipeToDismiss,
+ ) { isUnlocked, canSwipeToDismiss ->
+ upDestinationSceneKey(
+ isUnlocked = isUnlocked,
+ canSwipeToDismiss = canSwipeToDismiss,
+ )
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
upDestinationSceneKey(
isUnlocked = authenticationInteractor.isUnlocked.value,
+ canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
),
)
@@ -57,7 +65,12 @@
private fun upDestinationSceneKey(
isUnlocked: Boolean,
+ canSwipeToDismiss: Boolean,
): SceneKey {
- return if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen
+ return when {
+ canSwipeToDismiss -> SceneKey.Lockscreen
+ isUnlocked -> SceneKey.Gone
+ else -> SceneKey.Lockscreen
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index 37140ec..5209767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -37,8 +37,8 @@
init {
inflate(context, R.layout.battery_status_chip, this)
- roundedContainer = findViewById(R.id.rounded_container)
- batteryMeterView = findViewById(R.id.battery_meter_view)
+ roundedContainer = requireViewById(R.id.rounded_container)
+ batteryMeterView = requireViewById(R.id.battery_meter_view)
updateResources()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 823bb35..3120128 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -14,9 +14,11 @@
import android.os.Trace
import android.util.AttributeSet
import android.util.MathUtils.lerp
+import android.view.MotionEvent
import android.view.View
import android.view.animation.PathInterpolator
import com.android.app.animation.Interpolators
+import com.android.systemui.shade.TouchLogger
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
import com.android.systemui.util.leak.RotationUtils
@@ -234,6 +236,8 @@
}
}
+private const val TAG = "LightRevealScrim"
+
/**
* Scrim view that partially reveals the content underneath it using a [RadialGradient] with a
* transparent center. The center position, size, and stops of the gradient can be manipulated to
@@ -419,15 +423,14 @@
revealGradientCenter.y = top + (revealGradientHeight / 2f)
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
if (
- canvas == null ||
- revealGradientWidth <= 0 ||
- revealGradientHeight <= 0 ||
- revealAmount == 0f
+ revealGradientWidth <= 0 ||
+ revealGradientHeight <= 0 ||
+ revealAmount == 0f
) {
if (revealAmount < 1f) {
- canvas?.drawColor(revealGradientEndColor)
+ canvas.drawColor(revealGradientEndColor)
}
return
}
@@ -447,6 +450,10 @@
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ return TouchLogger.logDispatchTouch(TAG, event, super.dispatchTouchEvent(event))
+ }
+
private fun setPaintColorFilter() {
gradientPaint.colorFilter =
PorterDuffColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4710574..672796a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -474,7 +474,7 @@
}
if (endlistener != null) {
dragDownAnimator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
endlistener.invoke()
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index 750272d..17b4e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -66,7 +66,7 @@
inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
oldIn.recycle()
}
- val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+ val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
Bitmap.Config.ARGB_8888)
input = Allocation.createFromBitmap(renderScript, inBitmap,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0e20df6..6ad1395 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -272,7 +272,7 @@
blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
keyguardAnimator = null
wakeAndUnlockBlurRadius = 0f
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index eddb683..d1e0a71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -234,7 +234,7 @@
}
// Set the dot's view gravity to hug the status bar
- (corner.findViewById<View>(R.id.privacy_dot)
+ (corner.requireViewById<View>(R.id.privacy_dot)
.layoutParams as FrameLayout.LayoutParams)
.gravity = rotatedCorner.innerGravity()
}
@@ -255,7 +255,7 @@
// in every rotation. The only thing we need to check is rtl
val rtl = state.layoutRtl
val size = Point()
- tl.context.display.getRealSize(size)
+ tl.context.display?.getRealSize(size)
val currentRotation = RotationUtils.getExactRotation(tl.context)
val displayWidth: Int
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 6e8b8bd..1ad4620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -168,10 +168,8 @@
}
val keyFrame1Height = dotSize * 2
- val v = currentAnimatedView!!.view
- val chipVerticalCenter = v.top + v.measuredHeight / 2
- val height1 = ValueAnimator.ofInt(
- currentAnimatedView!!.view.measuredHeight, keyFrame1Height).apply {
+ val chipVerticalCenter = chipBounds.top + chipBounds.height() / 2
+ val height1 = ValueAnimator.ofInt(chipBounds.height(), keyFrame1Height).apply {
startDelay = 8.frames
duration = 6.frames
interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 903d485..c5de165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -179,15 +179,20 @@
}
if (weatherTarget != null) {
val clickIntent = weatherTarget.headerAction?.intent
- val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras, { v ->
- if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- activityStarter.startActivity(
- clickIntent,
- true, /* dismissShade */
- null,
- false)
+ val weatherData = weatherTarget.baseAction?.extras?.let { extras ->
+ WeatherData.fromBundle(
+ extras,
+ ) { _ ->
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ activityStarter.startActivity(
+ clickIntent,
+ true, /* dismissShade */
+ null,
+ false)
+ }
}
- })
+ }
+
if (weatherData != null) {
keyguardUpdateMonitor.sendWeatherData(weatherData)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index 16f1a45..1b43922 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -74,7 +74,7 @@
root.setTag(R.id.view_group_fade_helper_previous_value_tag, newAlpha)
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
endRunnable?.run()
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
deleted file mode 100644
index f04b24e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-/**
- * Handles ForegroundService and AppOp interactions with notifications.
- * Tags notifications with appOps
- * Lifetime extends notifications associated with an ongoing ForegroundService.
- * Filters out notifications that represent foreground services that are no longer running
- * Puts foreground service notifications into the FGS section. See {@link NotifCoordinators} for
- * section ordering priority.
- *
- * Previously this logic lived in
- * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
- * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
- * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
- */
-@CoordinatorScope
-public class AppOpsCoordinator implements Coordinator {
- private static final String TAG = "AppOpsCoordinator";
-
- private final ForegroundServiceController mForegroundServiceController;
- private final AppOpsController mAppOpsController;
- private final DelayableExecutor mMainExecutor;
-
- private NotifPipeline mNotifPipeline;
-
- @Inject
- public AppOpsCoordinator(
- ForegroundServiceController foregroundServiceController,
- AppOpsController appOpsController,
- @Main DelayableExecutor mainExecutor) {
- mForegroundServiceController = foregroundServiceController;
- mAppOpsController = appOpsController;
- mMainExecutor = mainExecutor;
- }
-
- @Override
- public void attach(NotifPipeline pipeline) {
- mNotifPipeline = pipeline;
-
- // filter out foreground service notifications that aren't necessary anymore
- mNotifPipeline.addPreGroupFilter(mNotifFilter);
-
- }
-
- public NotifSectioner getSectioner() {
- return mNotifSectioner;
- }
-
- /**
- * Filters out notifications that represent foreground services that are no longer running or
- * that already have an app notification with the appOps tagged to
- */
- private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
- @Override
- public boolean shouldFilterOut(NotificationEntry entry, long now) {
- StatusBarNotification sbn = entry.getSbn();
-
- // Filters out system-posted disclosure notifications when unneeded
- if (mForegroundServiceController.isDisclosureNotification(sbn)
- && !mForegroundServiceController.isDisclosureNeededForUser(
- sbn.getUser().getIdentifier())) {
- return true;
- }
- return false;
- }
- };
-
- /**
- * Puts colorized foreground service and call notifications into its own section.
- */
- private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
- NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
- @Override
- public boolean isInSection(ListEntry entry) {
- NotificationEntry notificationEntry = entry.getRepresentativeEntry();
- if (notificationEntry != null) {
- return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
- }
- return false;
- }
-
- private boolean isColorizedForegroundService(NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- return notification.isForegroundService()
- && notification.isColorized()
- && entry.getImportance() > IMPORTANCE_MIN;
- }
-
- private boolean isCall(NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- return entry.getImportance() > IMPORTANCE_MIN
- && notification.isStyle(Notification.CallStyle.class);
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
new file mode 100644
index 0000000..63997f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import android.app.Notification;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+
+import javax.inject.Inject;
+
+/**
+ * Handles sectioning for foreground service notifications.
+ * Puts non-min colorized foreground service notifications into the FGS section. See
+ * {@link NotifCoordinators} for section ordering priority.
+ */
+@CoordinatorScope
+public class ColorizedFgsCoordinator implements Coordinator {
+ private static final String TAG = "ColorizedCoordinator";
+
+ @Inject
+ public ColorizedFgsCoordinator() {
+ }
+
+ @Override
+ public void attach(NotifPipeline pipeline) {
+ }
+
+ public NotifSectioner getSectioner() {
+ return mNotifSectioner;
+ }
+
+
+ /**
+ * Puts colorized foreground service and call notifications into its own section.
+ */
+ private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
+ NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+ if (notificationEntry != null) {
+ return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
+ }
+ return false;
+ }
+
+ private boolean isColorizedForegroundService(NotificationEntry entry) {
+ Notification notification = entry.getSbn().getNotification();
+ return notification.isForegroundService()
+ && notification.isColorized()
+ && entry.getImportance() > IMPORTANCE_MIN;
+ }
+
+ private boolean isCall(NotificationEntry entry) {
+ Notification notification = entry.getSbn().getNotification();
+ return entry.getImportance() > IMPORTANCE_MIN
+ && notification.isStyle(Notification.CallStyle.class);
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 0ccab9e..226a957 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -33,34 +33,34 @@
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- sectionStyleProvider: SectionStyleProvider,
- featureFlags: FeatureFlags,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- appOpsCoordinator: AppOpsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- groupWhenCoordinator: GroupWhenCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator,
- dreamCoordinator: DreamCoordinator,
+ sectionStyleProvider: SectionStyleProvider,
+ featureFlags: FeatureFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ colorizedFgsCoordinator: ColorizedFgsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator,
+ dreamCoordinator: DreamCoordinator,
) : NotifCoordinators {
private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -79,7 +79,7 @@
mCoordinators.add(hideNotifsForOtherUsersCoordinator)
mCoordinators.add(keyguardCoordinator)
mCoordinators.add(rankingCoordinator)
- mCoordinators.add(appOpsCoordinator)
+ mCoordinators.add(colorizedFgsCoordinator)
mCoordinators.add(deviceProvisionedCoordinator)
mCoordinators.add(bubbleCoordinator)
mCoordinators.add(debugModeCoordinator)
@@ -106,7 +106,7 @@
// Manually add Ordered Sections
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
- mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
+ mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 106d11f..7d1cca8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
-import com.android.systemui.ForegroundServiceNotificationListener
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -70,7 +69,6 @@
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
- private val fgsNotifListener: ForegroundServiceNotificationListener,
private val featureFlags: FeatureFlags
) : NotificationsController {
@@ -105,7 +103,6 @@
notificationsMediaManager.setUpWithPresenter(presenter)
notificationLogger.setUpWithContainer(listContainer)
peopleSpaceWidgetManager.attach(notificationListener)
- fgsNotifListener.init()
}
// TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 38a1579..9c4aa07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -60,7 +60,7 @@
override fun onFinishInflate() {
super.onFinishInflate()
- appControlRow = findViewById(R.id.app_control)
+ appControlRow = requireViewById(R.id.app_control)
}
/**
@@ -143,9 +143,9 @@
lateinit var switch: Switch
override fun onFinishInflate() {
- iconView = findViewById(R.id.icon)
- channelName = findViewById(R.id.app_name)
- switch = findViewById(R.id.toggle)
+ iconView = requireViewById(R.id.icon)
+ channelName = requireViewById(R.id.app_name)
+ switch = requireViewById(R.id.toggle)
setOnClickListener { switch.toggle() }
}
@@ -174,9 +174,9 @@
override fun onFinishInflate() {
super.onFinishInflate()
- channelName = findViewById(R.id.channel_name)
- channelDescription = findViewById(R.id.channel_description)
- switch = findViewById(R.id.toggle)
+ channelName = requireViewById(R.id.channel_name)
+ channelDescription = requireViewById(R.id.channel_description)
+ switch = requireViewById(R.id.toggle)
switch.setOnCheckedChangeListener { _, b ->
channel?.let {
controller.proposeEditForChannel(it, if (b) it.importance else IMPORTANCE_NONE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index ed489a6c..d92d11b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1894,6 +1894,9 @@
return traceTag;
}
+ if (isSummaryWithChildren()) {
+ return traceTag + "(summary)";
+ }
Class<? extends Notification.Style> style =
getEntry().getSbn().getNotification().getNotificationStyle();
if (style == null) {
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 d71bc2f..5e3a67e 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
@@ -93,6 +93,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -3480,6 +3481,11 @@
return super.onTouchEvent(ev);
}
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+ }
+
void dispatchDownEventToScroller(MotionEvent ev) {
MotionEvent downEvent = MotionEvent.obtain(ev);
downEvent.setAction(MotionEvent.ACTION_DOWN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 2ccbc9f..baeae79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -914,8 +914,8 @@
val packages: Array<String> =
context.resources.getStringArray(R.array.system_ui_packages)
for (pkg in packages) {
- if (intent.component == null) break
- if (pkg == intent.component.packageName) {
+ val componentName = intent.component ?: break
+ if (pkg == componentName.packageName) {
return UserHandle(UserHandle.myUserId())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 7dcdc0b..97cb45a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -22,8 +22,6 @@
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.ConfigurationController
-
-import java.util.ArrayList
import javax.inject.Inject
@SysUISingleton
@@ -40,6 +38,7 @@
private var localeList: LocaleList? = null
private val context: Context
private var layoutDirection: Int
+ private var orientation = Configuration.ORIENTATION_UNDEFINED
init {
val currentConfig = context.resources.configuration
@@ -134,8 +133,18 @@
it.onThemeChanged()
}
}
+
+ val newOrientation = newConfig.orientation
+ if (orientation != newOrientation) {
+ orientation = newOrientation
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onOrientationChanged(orientation)
+ }
+ }
}
+
+
override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
listeners.add(listener)
listener.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f15dcc3..a1f12b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -81,6 +81,7 @@
private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
private final KeyguardBypassController mBypassController;
private final StatusBarStateController mStatusBarStateController;
+ private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
private final CommandQueue mCommandQueue;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
@@ -109,6 +110,7 @@
NotificationIconAreaController notificationIconAreaController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController stateController,
+ PhoneStatusBarTransitions phoneStatusBarTransitions,
KeyguardBypassController bypassController,
NotificationWakeUpCoordinator wakeUpCoordinator,
DarkIconDispatcher darkIconDispatcher,
@@ -156,6 +158,7 @@
});
mBypassController = bypassController;
mStatusBarStateController = stateController;
+ mPhoneStatusBarTransitions = phoneStatusBarTransitions;
mWakeUpCoordinator = wakeUpCoordinator;
mCommandQueue = commandQueue;
mKeyguardStateController = keyguardStateController;
@@ -203,6 +206,7 @@
@Override
public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
updateHeadsUpAndPulsingRoundness(entry);
+ mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
}
private void updateTopEntry() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 34bbd13..cdd410e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -32,6 +32,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.animation.requiresRemeasuring
/**
* Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -98,7 +99,7 @@
ambientIndicationArea?.let { nonNullAmbientIndicationArea ->
// remove old ambient indication from its parent
val originalAmbientIndicationView =
- oldBottomArea.findViewById<View>(R.id.ambient_indication_container)
+ oldBottomArea.requireViewById<View>(R.id.ambient_indication_container)
(originalAmbientIndicationView.parent as ViewGroup).removeView(
originalAmbientIndicationView
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 5c1f824b..38c3815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -87,6 +87,7 @@
private int mStatusBarPaddingEnd;
private int mMinDotWidth;
private View mSystemIconsContainer;
+ private View mSystemIcons;
private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow(
DarkChange.EMPTY);
@@ -119,6 +120,7 @@
protected void onFinishInflate() {
super.onFinishInflate();
mSystemIconsContainer = findViewById(R.id.system_icons_container);
+ mSystemIcons = findViewById(R.id.system_icons);
mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -167,6 +169,13 @@
mStatusIconContainer.getPaddingBottom()
);
+ mSystemIcons.setPaddingRelative(
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+ );
+
// Respect font size setting.
mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 15c6dcf..cc38405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,6 +31,8 @@
private final float mIconAlphaWhenOpaque;
+ private boolean mIsHeadsUp;
+
private View mStartSide, mStatusIcons, mBattery;
private Animator mCurrentAnimation;
@@ -52,15 +54,32 @@
return ObjectAnimator.ofFloat(v, "alpha", v.getAlpha(), toAlpha);
}
- private float getNonBatteryClockAlphaFor(int mode) {
- return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
- : !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
- : mIconAlphaWhenOpaque;
+ private float getStatusIconsAlphaFor(int mode) {
+ return getDefaultAlphaFor(mode);
+ }
+
+ private float getStartSideAlphaFor(int mode) {
+ // When there's a heads up notification, we need the start side icons to show regardless of
+ // lights out mode.
+ if (mIsHeadsUp) {
+ return getIconAlphaBasedOnOpacity(mode);
+ }
+ return getDefaultAlphaFor(mode);
}
private float getBatteryClockAlpha(int mode) {
return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
- : getNonBatteryClockAlphaFor(mode);
+ : getIconAlphaBasedOnOpacity(mode);
+ }
+
+ private float getDefaultAlphaFor(int mode) {
+ return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
+ : getIconAlphaBasedOnOpacity(mode);
+ }
+
+ private float getIconAlphaBasedOnOpacity(int mode) {
+ return !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
+ : mIconAlphaWhenOpaque;
}
private boolean isOpaque(int mode) {
@@ -74,19 +93,28 @@
applyMode(newMode, animate);
}
+ /** Informs this controller that the heads up notification state has changed. */
+ public void onHeadsUpStateChanged(boolean isHeadsUp) {
+ mIsHeadsUp = isHeadsUp;
+ // We want the icon to be fully visible when the HUN appears, so just immediately change the
+ // icon visibility and don't animate.
+ applyMode(getMode(), /* animate= */ false);
+ }
+
private void applyMode(int mode, boolean animate) {
if (mStartSide == null) return; // pre-init
- float newAlpha = getNonBatteryClockAlphaFor(mode);
- float newAlphaBC = getBatteryClockAlpha(mode);
+ float newStartSideAlpha = getStartSideAlphaFor(mode);
+ float newStatusIconsAlpha = getStatusIconsAlphaFor(mode);
+ float newBatteryAlpha = getBatteryClockAlpha(mode);
if (mCurrentAnimation != null) {
mCurrentAnimation.cancel();
}
if (animate) {
AnimatorSet anims = new AnimatorSet();
anims.playTogether(
- animateTransitionTo(mStartSide, newAlpha),
- animateTransitionTo(mStatusIcons, newAlpha),
- animateTransitionTo(mBattery, newAlphaBC)
+ animateTransitionTo(mStartSide, newStartSideAlpha),
+ animateTransitionTo(mStatusIcons, newStatusIconsAlpha),
+ animateTransitionTo(mBattery, newBatteryAlpha)
);
if (isLightsOut(mode)) {
anims.setDuration(LIGHTS_OUT_DURATION);
@@ -94,9 +122,9 @@
anims.start();
mCurrentAnimation = anims;
} else {
- mStartSide.setAlpha(newAlpha);
- mStatusIcons.setAlpha(newAlpha);
- mBattery.setAlpha(newAlphaBC);
+ mStartSide.setAlpha(newStartSideAlpha);
+ mStatusIcons.setAlpha(newStatusIconsAlpha);
+ mBattery.setAlpha(newBatteryAlpha);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index d546a84..83a040c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -208,25 +208,29 @@
ViewGroup.LayoutParams layoutParams = getLayoutParams();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
layoutParams.height = mStatusBarHeight - waterfallTopInset;
+ updatePaddings();
+ setLayoutParams(layoutParams);
+ }
- int statusBarPaddingTop = getResources().getDimensionPixelSize(
- R.dimen.status_bar_padding_top);
+ private void updatePaddings() {
int statusBarPaddingStart = getResources().getDimensionPixelSize(
R.dimen.status_bar_padding_start);
- int statusBarPaddingEnd = getResources().getDimensionPixelSize(
- R.dimen.status_bar_padding_end);
- View sbContents = findViewById(R.id.status_bar_contents);
- sbContents.setPaddingRelative(
+ findViewById(R.id.status_bar_contents).setPaddingRelative(
statusBarPaddingStart,
- statusBarPaddingTop,
- statusBarPaddingEnd,
+ getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
0);
findViewById(R.id.notification_lights_out)
.setPaddingRelative(0, statusBarPaddingStart, 0, 0);
- setLayoutParams(layoutParams);
+ findViewById(R.id.system_icons).setPaddingRelative(
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+ );
}
private void updateLayoutForCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 2affb817..931aedd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -75,13 +75,13 @@
}
override fun onViewAttached() {
- statusContainer = mView.findViewById(R.id.system_icons)
+ statusContainer = mView.requireViewById(R.id.system_icons)
statusContainer.setOnHoverListener(
statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
if (moveFromCenterAnimationController == null) return
- val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
- val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
+ val statusBarLeftSide: View = mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
+ val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content)
val viewsToAnimate = arrayOf(
statusBarLeftSide,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e82ac59..fc66138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -370,6 +370,9 @@
mScrimBehind = behindScrim;
mScrimInFront = scrimInFront;
updateThemeColors();
+ mNotificationsScrim.setScrimName(getScrimName(mNotificationsScrim));
+ mScrimBehind.setScrimName(getScrimName(mScrimBehind));
+ mScrimInFront.setScrimName(getScrimName(mScrimInFront));
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
mNotificationsScrim.enableRoundedCorners(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index c850d4f..ad18170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -117,11 +117,11 @@
* status bar area is contiguous.
*/
fun currentRotationHasCornerCutout(): Boolean {
- val cutout = context.display.cutout ?: return false
+ val cutout = checkNotNull(context.display).cutout ?: return false
val topBounds = cutout.boundingRectTop
val point = Point()
- context.display.getRealSize(point)
+ checkNotNull(context.display).getRealSize(point)
return topBounds.left <= 0 || topBounds.right >= point.x
}
@@ -161,7 +161,7 @@
*/
fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
- val displayCutout = context.display.cutout
+ val displayCutout = checkNotNull(context.display).cutout
val key = getCacheKey(rotation, displayCutout)
val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -198,7 +198,7 @@
fun getStatusBarContentAreaForRotation(
@Rotation rotation: Int
): Rect {
- val displayCutout = context.display.cutout
+ val displayCutout = checkNotNull(context.display).cutout
val key = getCacheKey(rotation, displayCutout)
return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
rotation, displayCutout, getResourcesForRotation(rotation, context), key)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index e8da951..1bceb29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -105,18 +105,18 @@
}
}
addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
if (lightRevealScrim.revealEffect !is CircleReveal) {
lightRevealScrim.revealAmount = 1f
}
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
lightRevealAnimationPlaying = false
interactionJankMonitor.end(CUJ_SCREEN_OFF)
}
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
interactionJankMonitor.begin(
notifShadeWindowControllerLazy.get().windowRootView, CUJ_SCREEN_OFF)
}
@@ -345,7 +345,7 @@
// portrait. If we're in another orientation, disable the screen off animation so we don't
// animate in the keyguard AOD UI sideways or upside down.
if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
- context.display.rotation != Surface.ROTATION_0) {
+ context.display?.rotation != Surface.ROTATION_0) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 270c592..1259477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -34,7 +34,7 @@
override fun onFinishInflate() {
super.onFinishInflate()
- text = findViewById(R.id.current_user_name)
- avatar = findViewById(R.id.current_user_avatar)
+ text = requireViewById(R.id.current_user_name)
+ avatar = requireViewById(R.id.current_user_avatar)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 6b80a9d..b2ef818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -42,5 +42,6 @@
default void onThemeChanged() {}
default void onLocaleListChanged() {}
default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
+ default void onOrientationChanged(int orientation) {}
}
}
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 4950482..ffb743f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -128,7 +128,8 @@
val prefs = userContextProvider.userContext.getSharedPreferences(
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
- val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+ val seededPackages =
+ prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
val controlsController = controlsComponent.getControlsController().get()
val componentsToSeed = mutableListOf<ComponentName>()
@@ -174,7 +175,8 @@
}
private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
- val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+ val seededPackages =
+ prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
val updatedPkgs = seededPackages.toMutableSet()
updatedPkgs.add(pkg)
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 710588c..63dcad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -18,6 +18,8 @@
import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,6 +40,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import dagger.Lazy;
@@ -49,6 +52,7 @@
import javax.inject.Inject;
/**
+ *
*/
@SysUISingleton
public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
@@ -103,7 +107,10 @@
*/
private boolean mSnappingKeyguardBackAfterSwipe = false;
+ private FeatureFlags mFeatureFlags;
+
/**
+ *
*/
@Inject
public KeyguardStateControllerImpl(
@@ -112,13 +119,15 @@
LockPatternUtils lockPatternUtils,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
KeyguardUpdateMonitorLogger logger,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
mContext = context;
mLogger = logger;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
+ mFeatureFlags = featureFlags;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -272,7 +281,8 @@
@Override
public boolean isKeyguardScreenRotationAllowed() {
return SystemProperties.getBoolean("lockscreen.rot_override", false)
- || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
+ || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation)
+ || mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 27aaa68..eb7d339 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -353,8 +353,8 @@
// before CoreStartables run, and will not be removed.
// In many cases, it reports the battery level of the stylus.
registerBatteryListener(deviceId)
- } else if (device.bluetoothAddress != null) {
- onStylusBluetoothConnected(deviceId, device.bluetoothAddress)
+ } else {
+ device.bluetoothAddress?.let { onStylusBluetoothConnected(deviceId, it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
index 72786ef..5ad9630 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -60,7 +60,7 @@
override fun getWidth(): Int {
val displayMetrics = context.resources.displayMetrics.apply {
- context.display.getRealMetrics(this)
+ checkNotNull(context.display).getRealMetrics(this)
}
return displayMetrics.widthPixels
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 088cd93..ee84580 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -52,22 +52,22 @@
override fun show() {
// need to call show() first in order to construct the listView
super.show()
- val listView = getListView()
+ listView?.apply {
+ isVerticalScrollBarEnabled = false
+ isHorizontalScrollBarEnabled = false
- listView.setVerticalScrollBarEnabled(false)
- listView.setHorizontalScrollBarEnabled(false)
+ // Creates a transparent spacer between items
+ val shape = ShapeDrawable()
+ shape.alpha = 0
+ divider = shape
+ dividerHeight = res.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_popup_divider_height)
- // Creates a transparent spacer between items
- val shape = ShapeDrawable()
- shape.setAlpha(0)
- listView.setDivider(shape)
- listView.setDividerHeight(res.getDimensionPixelSize(
- R.dimen.bouncer_user_switcher_popup_divider_height))
-
- val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
- listView.addHeaderView(createSpacer(height), null, false)
- listView.addFooterView(createSpacer(height), null, false)
- setWidth(findMaxWidth(listView))
+ val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
+ addHeaderView(createSpacer(height), null, false)
+ addFooterView(createSpacer(height), null, false)
+ setWidth(findMaxWidth(this))
+ }
super.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index e74232d..7f16e47 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -67,7 +67,7 @@
val resourceId: Int? = getGuestUserRecordNameResourceId(record)
return when {
resourceId != null -> context.getString(resourceId)
- record.info != null -> record.info.name
+ record.info != null -> checkNotNull(record.info.name)
else ->
context.getString(
getUserSwitcherActionTextResourceId(
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 56c5d3b..7866d76 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -223,11 +223,11 @@
}
}
- override fun dispatchDraw(canvas: Canvas?) {
- canvas?.save()
- canvas?.clipRect(boundsRect)
+ override fun dispatchDraw(canvas: Canvas) {
+ canvas.save()
+ canvas.clipRect(boundsRect)
super.dispatchDraw(canvas)
- canvas?.restore()
+ canvas.restore()
}
private fun updateBounds() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 9ba21da..dfee591 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -38,6 +38,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -144,6 +145,7 @@
private lateinit var testableResources: TestableResources
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var authenticationInteractor: AuthenticationInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var underTest: KeyguardSecurityContainerController
@@ -207,6 +209,11 @@
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
+ authenticationInteractor =
+ sceneTestUtils.authenticationInteractor(
+ repository = sceneTestUtils.authenticationRepository(),
+ sceneInteractor = sceneInteractor
+ )
underTest =
KeyguardSecurityContainerController(
@@ -237,7 +244,7 @@
userInteractor,
faceAuthAccessibilityDelegate,
) {
- sceneInteractor
+ authenticationInteractor
}
}
@@ -493,30 +500,6 @@
}
@Test
- fun showNextSecurityScreenOrFinish_SimPinToAnotherSimPin_None() {
- // GIVEN the current security method is SimPin
- whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
- whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
- .thenReturn(false)
- underTest.showSecurityScreen(SecurityMode.SimPin)
-
- // WHEN a request is made from the SimPin screens to show the next security method
- whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
- .thenReturn(SecurityMode.SimPin)
- whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
-
- underTest.showNextSecurityScreenOrFinish(
- /* authenticated= */ true,
- TARGET_USER_ID,
- /* bypassSecondaryLockScreen= */ true,
- SecurityMode.SimPin
- )
-
- // THEN the next security method of None will dismiss keyguard.
- verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
- }
-
- @Test
fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
val registeredSwipeListener = registeredSwipeListener
whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
@@ -753,7 +736,7 @@
}
@Test
- fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+ fun dismissesKeyguard_whenSceneChangesToGone() =
sceneTestUtils.testScope.runTest {
featureFlags.set(Flags.SCENE_CONTAINER, true)
@@ -790,12 +773,32 @@
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+ // While listening, moving back to the lockscreen scene does not dismiss the keyguard
+ // again.
+ clearInvocations(viewMediatorCallback)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ SceneKey.Gone,
+ SceneKey.Lockscreen,
+ flowOf(.5f)
+ )
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
// While listening, moving back to the bouncer scene does not dismiss the keyguard
// again.
clearInvocations(viewMediatorCallback)
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
sceneTransitionStateFlow.value =
- ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+ ObservableTransitionState.Transition(
+ SceneKey.Lockscreen,
+ SceneKey.Bouncer,
+ flowOf(.5f)
+ )
runCurrent()
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
@@ -815,7 +818,21 @@
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
- // While not listening, moving back to the bouncer does not dismiss the keyguard.
+ // While not listening, moving to the lockscreen does not dismiss the keyguard.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ SceneKey.Gone,
+ SceneKey.Lockscreen,
+ flowOf(.5f)
+ )
+ runCurrent()
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ runCurrent()
+ verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+ // While not listening, moving to the bouncer does not dismiss the keyguard.
sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
sceneTransitionStateFlow.value =
ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
@@ -826,12 +843,15 @@
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// Reattaching the view starts listening again so moving from the bouncer scene to the
- // gone
- // scene now does dismiss the keyguard again.
+ // gone scene now does dismiss the keyguard again, this time from lockscreen.
underTest.onViewAttached()
sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
sceneTransitionStateFlow.value =
- ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+ ObservableTransitionState.Transition(
+ SceneKey.Lockscreen,
+ SceneKey.Gone,
+ flowOf(.5f)
+ )
runCurrent()
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
index 9fcb9c8..7c2550f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
@@ -47,10 +47,10 @@
@Test
fun testOnLayout() {
- underTest.onLayout(100)
+ underTest.onLayout(100, 100)
verify(background).cornerRadius = 50f
reset(background)
- underTest.onLayout(100)
+ underTest.onLayout(100, 100)
verify(background, never()).cornerRadius = anyFloat()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 01d3a39..ea7cc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.decor.FaceScanningProviderFactory
import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,8 @@
R.bool.config_fillMainBuiltInDisplayCutout,
true
)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true)
underTest =
FaceScanningProviderFactory(
authController,
@@ -87,7 +91,8 @@
statusBarStateController,
keyguardUpdateMonitor,
mock(Executor::class.java),
- ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+ ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+ featureFlags,
)
whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
deleted file mode 100644
index b47b08c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ /dev/null
@@ -1,487 +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.systemui;
-
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.fail;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ForegroundServiceControllerTest extends SysuiTestCase {
- private ForegroundServiceController mFsc;
- private ForegroundServiceNotificationListener mListener;
- private NotifCollectionListener mCollectionListener;
- @Mock private AppOpsController mAppOpsController;
- @Mock private Handler mMainHandler;
- @Mock private NotifPipeline mNotifPipeline;
-
- @Before
- public void setUp() throws Exception {
- // allow the TestLooper to be asserted as the main thread these tests
- allowTestableLooperAsMainThread();
-
- MockitoAnnotations.initMocks(this);
- mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
- mListener = new ForegroundServiceNotificationListener(
- mContext, mFsc, mNotifPipeline);
- mListener.init();
- ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
- ArgumentCaptor.forClass(NotifCollectionListener.class);
- verify(mNotifPipeline).addCollectionListener(
- entryListenerCaptor.capture());
- mCollectionListener = entryListenerCaptor.getValue();
- }
-
- @Test
- public void testAppOpsChangedCalledFromBgThread() {
- try {
- // WHEN onAppOpChanged is called from a different thread than the MainLooper
- disallowTestableLooperAsMainThread();
- NotificationEntry entry = createFgEntry();
- mFsc.onAppOpChanged(
- AppOpsManager.OP_CAMERA,
- entry.getSbn().getUid(),
- entry.getSbn().getPackageName(),
- true);
-
- // This test is run on the TestableLooper, which is not the MainLooper, so
- // we expect an exception to be thrown
- fail("onAppOpChanged shouldn't be allowed to be called from a bg thread.");
- } catch (IllegalStateException e) {
- // THEN expect an exception
- }
- }
-
- @Test
- public void testAppOpsCRUD() {
- // no crash on remove that doesn't exist
- mFsc.onAppOpChanged(9, 1000, "pkg1", false);
- assertNull(mFsc.getAppOps(0, "pkg1"));
-
- // multiuser & multipackage
- mFsc.onAppOpChanged(8, 50, "pkg1", true);
- mFsc.onAppOpChanged(1, 60, "pkg3", true);
- mFsc.onAppOpChanged(7, 500000, "pkg2", true);
-
- assertEquals(1, mFsc.getAppOps(0, "pkg1").size());
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-
- assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
- assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
- assertEquals(1, mFsc.getAppOps(0, "pkg3").size());
- assertTrue(mFsc.getAppOps(0, "pkg3").contains(1));
-
- // multiple ops for the same package
- mFsc.onAppOpChanged(9, 50, "pkg1", true);
- mFsc.onAppOpChanged(5, 50, "pkg1", true);
-
- assertEquals(3, mFsc.getAppOps(0, "pkg1").size());
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(9));
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
- assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
- assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
- // remove one of the multiples
- mFsc.onAppOpChanged(9, 50, "pkg1", false);
- assertEquals(2, mFsc.getAppOps(0, "pkg1").size());
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
- assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
- // remove last op
- mFsc.onAppOpChanged(1, 60, "pkg3", false);
- assertNull(mFsc.getAppOps(0, "pkg3"));
- }
-
- @Test
- public void testDisclosurePredicate() {
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android",
- SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
- null, Notification.FLAG_NO_CLEAR);
-
- assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure));
- assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1));
- }
-
- @Test
- public void testNeedsDisclosureAfterRemovingUnrelatedNotification() {
- final String PKG1 = "com.example.app100";
-
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
- // first add a normal notification
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
- // nothing required yet
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- // now the app starts a fg service
- entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- // add the fg notification
- entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
- // remove the boring notification
- entryRemoved(sbn_user1_app1);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered
- entryRemoved(sbn_user1_app1_fg);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- }
-
- @Test
- public void testSimpleAddRemove() {
- final String PKG1 = "com.example.app1";
- final String PKG2 = "com.example.app2";
-
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-
- // no services are "running"
- entryAdded(makeMockDisclosure(USERID_ONE, null),
- NotificationManager.IMPORTANCE_DEFAULT);
-
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // switch to different package
- entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too
-
- entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- }
-
- @Test
- public void testDisclosureBasic() {
- final String PKG1 = "com.example.app0";
-
- StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
- 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
- StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
- entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // let's take out the other notification and see what happens.
-
- entryRemoved(sbn_user1_app1);
- assertFalse(
- mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
- StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1);
- sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
- entryUpdated(sbn_user1_app1_fg_sneaky,
- NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // ok, ok, we'll put it back
- sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
- entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- entryRemoved(sbn_user1_app1_fg_sneaky);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
- // now let's test an upgrade
- entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- entryUpdated(sbn_user1_app1,
- NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
- // remove it, make sure we're out of compliance again
- entryRemoved(sbn_user1_app1); // was fg, should return true
- entryRemoved(sbn_user1_app1);
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
- // importance upgrade
- entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
- assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- entryUpdated(sbn_user1_app1_fg,
- NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
- // finally, let's turn off the service
- entryAdded(makeMockDisclosure(USERID_ONE, null),
- NotificationManager.IMPORTANCE_DEFAULT);
-
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
- assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
- }
-
- @Test
- public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
- // no notifications nor app op signals that this package/userId requires system alert
- // warning
- assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, "any"));
- }
-
- @Test
- public void testCustomLayouts_systemAlertWarningRequired() {
- // GIVEN a notification with a custom layout
- final String pkg = "com.example.app0";
- StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
- false);
-
- // WHEN the custom layout entry is added
- entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
- // THEN a system alert warning is required since there aren't any notifications that can
- // display the app ops
- assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- @Test
- public void testStandardLayoutExists_noSystemAlertWarningRequired() {
- // GIVEN two notifications (one with a custom layout, the other with a standard layout)
- final String pkg = "com.example.app0";
- StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
- false);
- StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
- // WHEN the entries are added
- entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
- // THEN no system alert warning is required, since there is at least one notification
- // with a standard layout that can display the app ops on the notification
- assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- @Test
- public void testStandardLayoutRemoved_systemAlertWarningRequired() {
- // GIVEN two notifications (one with a custom layout, the other with a standard layout)
- final String pkg = "com.example.app0";
- StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
- false);
- StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
- // WHEN the entries are added and then the standard layout notification is removed
- entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryRemoved(standardLayoutNotif);
-
- // THEN a system alert warning is required since there aren't any notifications that can
- // display the app ops
- assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- @Test
- public void testStandardLayoutUpdatedToCustomLayout_systemAlertWarningRequired() {
- // GIVEN a standard layout notification and then an updated version with a customLayout
- final String pkg = "com.example.app0";
- StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
- StatusBarNotification updatedToCustomLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, false);
-
- // WHEN the entries is added and then updated to a custom layout
- entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
- entryUpdated(updatedToCustomLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
- // THEN a system alert warning is required since there aren't any notifications that can
- // display the app ops
- assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
- }
-
- private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag,
- int flags) {
- final Notification n = mock(Notification.class);
- n.extras = new Bundle();
- n.flags = flags;
- return makeMockSBN(userId, pkg, id, tag, n);
- }
-
- private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
- Notification n) {
- final StatusBarNotification sbn = mock(StatusBarNotification.class);
- when(sbn.getNotification()).thenReturn(n);
- when(sbn.getId()).thenReturn(id);
- when(sbn.getPackageName()).thenReturn(pkg);
- when(sbn.getTag()).thenReturn(tag);
- when(sbn.getUserId()).thenReturn(userid);
- when(sbn.getUser()).thenReturn(new UserHandle(userid));
- when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag);
- return sbn;
- }
-
- private StatusBarNotification makeMockSBN(int uid, String pkg, int id,
- boolean usesStdLayout) {
- StatusBarNotification sbn = makeMockSBN(uid, pkg, id, "foo", 0);
- if (usesStdLayout) {
- sbn.getNotification().contentView = null;
- sbn.getNotification().headsUpContentView = null;
- sbn.getNotification().bigContentView = null;
- } else {
- sbn.getNotification().contentView = mock(RemoteViews.class);
- }
- return sbn;
- }
-
- private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id,
- boolean usesStdLayout) {
- StatusBarNotification sbn =
- makeMockSBN(uid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE);
- if (usesStdLayout) {
- sbn.getNotification().contentView = null;
- sbn.getNotification().headsUpContentView = null;
- sbn.getNotification().bigContentView = null;
- } else {
- sbn.getNotification().contentView = mock(RemoteViews.class);
- }
- return sbn;
- }
-
- private StatusBarNotification makeMockFgSBN(int uid, String pkg) {
- return makeMockSBN(uid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
- }
-
- private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) {
- final Notification n = mock(Notification.class);
- n.flags = Notification.FLAG_ONGOING_EVENT;
- final Bundle extras = new Bundle();
- if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
- n.extras = extras;
- n.when = System.currentTimeMillis() - 10000; // ten seconds ago
- final StatusBarNotification sbn = makeMockSBN(userid, "android",
- SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
- null, n);
- sbn.getNotification().extras = extras;
- return sbn;
- }
-
- private NotificationEntry addFgEntry() {
- NotificationEntry entry = createFgEntry();
- mCollectionListener.onEntryAdded(entry);
- return entry;
- }
-
- private NotificationEntry createFgEntry() {
- return new NotificationEntryBuilder()
- .setSbn(makeMockFgSBN(0, TEST_PACKAGE_NAME, 1000, true))
- .setImportance(NotificationManager.IMPORTANCE_DEFAULT)
- .build();
- }
-
- private void entryRemoved(StatusBarNotification notification) {
- mCollectionListener.onEntryRemoved(
- new NotificationEntryBuilder()
- .setSbn(notification)
- .build(),
- REASON_APP_CANCEL);
- }
-
- private void entryAdded(StatusBarNotification notification, int importance) {
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(notification)
- .setImportance(importance)
- .build();
- mCollectionListener.onEntryAdded(entry);
- }
-
- private void entryUpdated(StatusBarNotification notification, int importance) {
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(notification)
- .setImportance(importance)
- .build();
- mCollectionListener.onEntryUpdated(entry);
- }
-
- @UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
- @UserIdInt private static final int USERID_TWO = USERID_ONE + 1;
- private static final String TEST_PACKAGE_NAME = "test";
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 796e665..f81ef10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -92,6 +92,8 @@
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -226,13 +228,16 @@
doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
+ FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true);
mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
BOUNDS_POSITION_TOP,
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mExecutor,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ featureFlags));
mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 56f8160..e96ad87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -682,6 +682,36 @@
}
@Test
+ public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ View closeButton = getInternalView(R.id.close_button);
+ View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
+ View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
+ View topRightCorner = getInternalView(R.id.top_right_corner);
+ View topLeftCorner = getInternalView(R.id.top_left_corner);
+
+ assertEquals(View.VISIBLE, closeButton.getVisibility());
+ assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
+ assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
+ assertEquals(View.VISIBLE, topRightCorner.getVisibility());
+ assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
+
+ assertEquals(View.GONE, closeButton.getVisibility());
+ assertEquals(View.GONE, bottomRightCorner.getVisibility());
+ assertEquals(View.GONE, bottomLeftCorner.getVisibility());
+ assertEquals(View.GONE, topRightCorner.getVisibility());
+ assertEquals(View.GONE, topLeftCorner.getVisibility());
+ }
+
+ @Test
public void enableWindowMagnification_hasA11yWindowTitle() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index fc7d20a..707b1b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -33,6 +33,7 @@
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -605,7 +606,68 @@
assertThat(hintedPinLength).isNull()
}
- private fun switchToScene(sceneKey: SceneKey) {
- sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ @Test
+ fun isLockscreenDismissed() =
+ testScope.runTest {
+ val isLockscreenDismissed by collectLastValue(underTest.isLockscreenDismissed)
+ // Start on lockscreen.
+ switchToScene(SceneKey.Lockscreen)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes down to reveal shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes down to reveal quick settings.
+ switchToScene(SceneKey.QuickSettings)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes up to go back to shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes up to reveal bouncer.
+ switchToScene(SceneKey.Bouncer)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user hits back to return to lockscreen.
+ switchToScene(SceneKey.Lockscreen)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user swipes up to reveal bouncer.
+ switchToScene(SceneKey.Bouncer)
+ assertThat(isLockscreenDismissed).isFalse()
+
+ // The user enters correct credentials and goes to gone.
+ switchToScene(SceneKey.Gone)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes down to reveal shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes down to reveal quick settings.
+ switchToScene(SceneKey.QuickSettings)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes up to go back to shade.
+ switchToScene(SceneKey.Shade)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The user swipes up to go back to gone.
+ switchToScene(SceneKey.Gone)
+ assertThat(isLockscreenDismissed).isTrue()
+
+ // The device goes to sleep, returning to the lockscreen.
+ switchToScene(SceneKey.Lockscreen)
+ assertThat(isLockscreenDismissed).isFalse()
+ }
+
+ private fun TestScope.switchToScene(sceneKey: SceneKey) {
+ val model = SceneModel(sceneKey)
+ val loggingReason = "reason"
+ sceneInteractor.changeScene(model, loggingReason)
+ sceneInteractor.onSceneChanged(model, loggingReason)
+ runCurrent()
}
}
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 e3e6130..4e52e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -139,6 +139,7 @@
@Before
fun setup() {
featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
+ featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@After
@@ -151,7 +152,10 @@
@Test
fun testNotifiesAnimatedIn() {
initializeFingerprintContainer()
- verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+ verify(callback).onDialogAnimatedIn(
+ authContainer?.requestId ?: 0L,
+ true /* startFingerprintNow */
+ )
}
@Test
@@ -196,7 +200,10 @@
waitForIdleSync()
// attaching the view resets the state and allows this to happen again
- verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+ verify(callback).onDialogAnimatedIn(
+ authContainer?.requestId ?: 0L,
+ true /* startFingerprintNow */
+ )
}
@Test
@@ -211,7 +218,10 @@
// the first time is triggered by initializeFingerprintContainer()
// the second time was triggered by dismissWithoutCallback()
- verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+ verify(callback, times(2)).onDialogAnimatedIn(
+ authContainer?.requestId ?: 0L,
+ true /* startFingerprintNow */
+ )
}
@Test
@@ -517,10 +527,11 @@
{ authBiometricFingerprintViewModel },
{ promptSelectorInteractor },
{ bpCredentialInteractor },
- PromptViewModel(promptSelectorInteractor, vibrator),
+ PromptViewModel(promptSelectorInteractor, vibrator, featureFlags),
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
- fakeExecutor
+ fakeExecutor,
+ vibrator
) {
override fun postOnAnimation(runnable: Runnable) {
runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 3d4171f..bf2020b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -197,6 +197,8 @@
private ArgumentCaptor<String> mMessageCaptor;
@Mock
private Resources mResources;
+ @Mock
+ private VibratorHelper mVibratorHelper;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -1097,7 +1099,7 @@
() -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel,
mInteractionJankMonitor, mHandler,
- mBackgroundExecutor, mUdfpsUtils);
+ mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 2b08c66..994db46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -164,7 +164,7 @@
context.addMockSystemService(WindowManager::class.java, windowManager)
whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
- whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+ whenEver(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
.thenReturn(mock(LottieAnimationView::class.java))
with(mock(ViewPropertyAnimator::class.java)) {
whenEver(sideFpsView.animate()).thenReturn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 40b1f20..7e6b74a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -19,6 +19,7 @@
import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.view.HapticFeedbackConstants
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -33,6 +34,8 @@
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
@@ -71,13 +74,15 @@
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
+ private val featureFlags = FakeFeatureFlags()
@Before
fun setup() {
selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
selector.resetPrompt()
- viewModel = PromptViewModel(selector, vibrator)
+ viewModel = PromptViewModel(selector, vibrator, featureFlags)
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@Test
@@ -149,6 +154,29 @@
verify(vibrator, never()).vibrateAuthError(any())
}
+ @Test
+ fun playSuccessHaptic_onwayHapticsEnabled_SetsConfirmConstant() = runGenericTest {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+ viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+ if (expectConfirmation) {
+ viewModel.confirmAuthenticated()
+ }
+
+ val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+ }
+
+ @Test
+ fun playErrorHaptic_onwayHapticsEnabled_SetsRejectConstant() = runGenericTest {
+ featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+ viewModel.showTemporaryError("test", "messageAfterError", false)
+
+ val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+ assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+ }
+
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
expectConfirmation: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index baa5ee8..1dcb55d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.keyguard.util.IndicationHelper
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,7 @@
private lateinit var configurationRepository: FakeConfigurationRepository
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var trustRepository: FakeTrustRepository
+ private lateinit var powerRepository: FakePowerRepository
@Mock private lateinit var indicationHelper: IndicationHelper
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -102,6 +105,7 @@
set(Flags.DELAY_BOUNCER, false)
}
trustRepository = FakeTrustRepository()
+ powerRepository = FakePowerRepository()
underTest =
OccludingAppDeviceEntryInteractor(
BiometricMessageInteractor(
@@ -145,6 +149,14 @@
testScope.backgroundScope,
mockedContext,
activityStarter,
+ PowerInteractor(
+ powerRepository,
+ keyguardRepository,
+ falsingCollector = mock(),
+ screenOffAnimationController = mock(),
+ statusBarStateController = mock(),
+ ),
+ FakeFeatureFlags().apply { set(Flags.FP_LISTEN_OCCLUDING_APPS, true) },
)
}
@@ -160,6 +172,18 @@
}
@Test
+ fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(true)
+ powerRepository.setInteractive(false)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
testScope.runTest {
givenOnOccludingApp(false)
@@ -291,6 +315,7 @@
}
private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+ powerRepository.setInteractive(true)
keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
keyguardRepository.setKeyguardShowing(isOnOccludingApp)
bouncerRepository.setPrimaryShow(!isOnOccludingApp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
new file mode 100644
index 0000000..53c04cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.model.SysUiState
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
+import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Integration test cases for the Scene Framework.
+ *
+ * **Principles**
+ * * All test cases here should be done from the perspective of the view-models of the system.
+ * * Focus on happy paths, let smaller unit tests focus on failure cases.
+ * * These are _integration_ tests and, as such, are larger and harder to maintain than unit tests.
+ * Therefore, when adding or modifying test cases, consider whether what you're testing is better
+ * covered by a more granular unit test.
+ * * Please reuse the helper methods in this class (for example, [putDeviceToSleep] or
+ * [emulateUserDrivenTransition]).
+ * * All tests start with the device locked and with a PIN auth method. The class offers useful
+ * methods like [setAuthMethod], [unlockDevice], [lockDevice], etc. to help you set up a starting
+ * state that makes more sense for your test case.
+ * * All helper methods in this class make assertions that are meant to make sure that they're only
+ * being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
+ * put to sleep a device that's already asleep, etc.).
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneFrameworkIntegrationTest : SysuiTestCase() {
+
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+
+ private val sceneContainerConfig = utils.fakeSceneContainerConfig()
+ private val sceneRepository =
+ utils.fakeSceneContainerRepository(
+ containerConfig = sceneContainerConfig,
+ )
+ private val sceneInteractor =
+ utils.sceneInteractor(
+ repository = sceneRepository,
+ )
+
+ private val authenticationRepository = utils.authenticationRepository()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = authenticationRepository,
+ sceneInteractor = sceneInteractor,
+ )
+
+ private val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
+ )
+ private val sceneContainerViewModel =
+ SceneContainerViewModel(
+ interactor = sceneInteractor,
+ )
+ .apply { setTransitionState(transitionState) }
+
+ private val bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+ private val bouncerViewModel =
+ utils.bouncerViewModel(
+ bouncerInteractor = bouncerInteractor,
+ authenticationInteractor = authenticationInteractor,
+ )
+
+ private val lockscreenSceneViewModel =
+ LockscreenSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractor = bouncerInteractor,
+ )
+
+ private val shadeSceneViewModel =
+ ShadeSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractor = bouncerInteractor,
+ )
+
+ private val keyguardRepository = utils.keyguardRepository()
+ private val keyguardInteractor =
+ utils.keyguardInteractor(
+ repository = keyguardRepository,
+ )
+
+ @Before
+ fun setUp() {
+ val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) }
+
+ authenticationRepository.setUnlocked(false)
+
+ val displayTracker = FakeDisplayTracker(context)
+ val sysUiState = SysUiState(displayTracker)
+ val startable =
+ SceneContainerStartable(
+ applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
+ authenticationInteractor = authenticationInteractor,
+ keyguardInteractor = keyguardInteractor,
+ featureFlags = featureFlags,
+ sysUiState = sysUiState,
+ displayId = displayTracker.defaultDisplayId,
+ sceneLogger = mock(),
+ )
+ startable.start()
+
+ assertWithMessage("Initial scene key mismatch!")
+ .that(sceneContainerViewModel.currentScene.value.key)
+ .isEqualTo(sceneContainerConfig.initialSceneKey)
+ assertWithMessage("Initial scene container visibility mismatch!")
+ .that(sceneContainerViewModel.isVisible.value)
+ .isTrue()
+ }
+
+ @Test
+ fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
+ testScope.runTest {
+ lockscreenSceneViewModel.onLockButtonClicked()
+ assertCurrentScene(SceneKey.Bouncer)
+ emulateUiSceneTransition()
+
+ enterPin()
+ assertCurrentScene(SceneKey.Gone)
+ emulateUiSceneTransition(
+ expectedVisible = false,
+ )
+ }
+
+ @Test
+ fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
+ testScope.runTest {
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+
+ enterPin()
+ assertCurrentScene(SceneKey.Gone)
+ emulateUiSceneTransition(
+ expectedVisible = false,
+ )
+ }
+
+ @Test
+ fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() =
+ testScope.runTest {
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+ }
+
+ @Test
+ fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+ testScope.runTest {
+ val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ // Emulate a user swipe to the shade scene.
+ emulateUserDrivenTransition(to = SceneKey.Shade)
+ assertCurrentScene(SceneKey.Shade)
+
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Lockscreen)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+ }
+
+ @Test
+ fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
+ testScope.runTest {
+ val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+ setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ // Emulate a user swipe to dismiss the lockscreen.
+ emulateUserDrivenTransition(to = SceneKey.Gone)
+ assertCurrentScene(SceneKey.Gone)
+
+ // Emulate a user swipe to the shade scene.
+ emulateUserDrivenTransition(to = SceneKey.Shade)
+ assertCurrentScene(SceneKey.Shade)
+
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+ emulateUserDrivenTransition(
+ to = upDestinationSceneKey,
+ )
+ }
+
+ @Test
+ fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() =
+ testScope.runTest {
+ setAuthMethod(AuthenticationMethodModel.None)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Gone)
+ }
+
+ @Test
+ fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() =
+ testScope.runTest {
+ setAuthMethod(AuthenticationMethodModel.Swipe)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun deviceGoesToSleep_switchesToLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+
+ putDeviceToSleep()
+ assertCurrentScene(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun deviceGoesToSleep_wakeUp_unlock() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ putDeviceToSleep()
+ assertCurrentScene(SceneKey.Lockscreen)
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ }
+
+ @Test
+ fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+ wakeUpDevice()
+ assertCurrentScene(SceneKey.Gone)
+ }
+
+ @Test
+ fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ val upDestinationSceneKey by
+ collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
+ testScope.runTest {
+ unlockDevice()
+ assertCurrentScene(SceneKey.Gone)
+ putDeviceToSleep(instantlyLockDevice = false)
+ assertCurrentScene(SceneKey.Lockscreen)
+
+ // Pretend like the timeout elapsed and now lock the device.
+ lockDevice()
+ assertCurrentScene(SceneKey.Lockscreen)
+ }
+
+ /**
+ * Asserts that the current scene in the view-model matches what's expected.
+ *
+ * Note that this doesn't assert what the current scene is in the UI.
+ */
+ private fun TestScope.assertCurrentScene(expected: SceneKey) {
+ runCurrent()
+ assertWithMessage("Current scene mismatch!")
+ .that(sceneContainerViewModel.currentScene.value.key)
+ .isEqualTo(expected)
+ }
+
+ /**
+ * Returns the [SceneKey] of the current scene as displayed in the UI.
+ *
+ * This can be different than the value in [SceneContainerViewModel.currentScene], by design, as
+ * the UI must gradually transition between scenes.
+ */
+ private fun getCurrentSceneInUi(): SceneKey {
+ return when (val state = transitionState.value) {
+ is ObservableTransitionState.Idle -> state.scene
+ is ObservableTransitionState.Transition -> state.fromScene
+ }
+ }
+
+ /** Updates the current authentication method and related states in the data layer. */
+ private fun TestScope.setAuthMethod(
+ authMethod: DomainLayerAuthenticationMethodModel,
+ ) {
+ // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
+ // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
+ // is not an observable that can trigger a new evaluation.
+ authenticationRepository.setLockscreenEnabled(authMethod !is AuthenticationMethodModel.None)
+ authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+ if (!authMethod.isSecure) {
+ // When the auth method is not secure, the device is never considered locked.
+ authenticationRepository.setUnlocked(true)
+ }
+ runCurrent()
+ }
+
+ /**
+ * Emulates a complete transition in the UI from whatever the current scene is in the UI to
+ * whatever the current scene should be, based on the value in
+ * [SceneContainerViewModel.onSceneChanged].
+ *
+ * This should post a series of values into [transitionState] to emulate a gradual scene
+ * transition and culminate with a call to [SceneContainerViewModel.onSceneChanged].
+ *
+ * The method asserts that a transition is actually required. E.g. it will fail if the current
+ * scene in [transitionState] is already caught up with the scene in
+ * [SceneContainerViewModel.currentScene].
+ *
+ * @param expectedVisible Whether [SceneContainerViewModel.isVisible] should be set at the end
+ * of the UI transition.
+ */
+ private fun TestScope.emulateUiSceneTransition(
+ expectedVisible: Boolean = true,
+ ) {
+ val to = sceneContainerViewModel.currentScene.value
+ val from = getCurrentSceneInUi()
+ assertWithMessage("Cannot transition to ${to.key} as the UI is already on that scene!")
+ .that(to.key)
+ .isNotEqualTo(from)
+
+ // Begin to transition.
+ val progressFlow = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = getCurrentSceneInUi(),
+ toScene = to.key,
+ progress = progressFlow,
+ )
+ runCurrent()
+
+ // Report progress of transition.
+ while (progressFlow.value < 1f) {
+ progressFlow.value += 0.2f
+ runCurrent()
+ }
+
+ // End the transition and report the change.
+ transitionState.value = ObservableTransitionState.Idle(to.key)
+
+ sceneContainerViewModel.onSceneChanged(to)
+ runCurrent()
+
+ assertWithMessage("Visibility mismatch after scene transition from $from to ${to.key}!")
+ .that(sceneContainerViewModel.isVisible.value)
+ .isEqualTo(expectedVisible)
+ }
+
+ /**
+ * Emulates a fire-and-forget user action (a fling or back, not a pointer-tracking swipe) that
+ * causes a scene change to the [to] scene.
+ *
+ * This also includes the emulation of the resulting UI transition that culminates with the UI
+ * catching up with the requested scene change (see [emulateUiSceneTransition]).
+ *
+ * @param to The scene to transition to.
+ */
+ private fun TestScope.emulateUserDrivenTransition(
+ to: SceneKey?,
+ ) {
+ checkNotNull(to)
+
+ sceneInteractor.changeScene(SceneModel(to), "reason")
+ assertThat(sceneContainerViewModel.currentScene.value.key).isEqualTo(to)
+
+ emulateUiSceneTransition(
+ expectedVisible = to != SceneKey.Gone,
+ )
+ }
+
+ /**
+ * Locks the device immediately (without delay).
+ *
+ * Asserts the device to be lockable (e.g. that the current authentication is secure).
+ *
+ * Not to be confused with [putDeviceToSleep], which may also instantly lock the device.
+ */
+ private suspend fun TestScope.lockDevice() {
+ val authMethod = authenticationInteractor.getAuthenticationMethod()
+ assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!")
+ .that(authMethod.isSecure)
+ .isTrue()
+
+ authenticationRepository.setUnlocked(false)
+ runCurrent()
+ }
+
+ /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
+ private fun TestScope.unlockDevice() {
+ assertWithMessage("Cannot unlock a device that's already unlocked!")
+ .that(authenticationInteractor.isUnlocked.value)
+ .isFalse()
+
+ lockscreenSceneViewModel.onLockButtonClicked()
+ runCurrent()
+ emulateUiSceneTransition()
+
+ enterPin()
+ emulateUiSceneTransition(
+ expectedVisible = false,
+ )
+ }
+
+ /**
+ * Enters the correct PIN in the bouncer UI.
+ *
+ * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+ * before proceeding.
+ *
+ * Does not assert that the device is locked or unlocked.
+ */
+ private fun TestScope.enterPin() {
+ assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
+ .that(getCurrentSceneInUi())
+ .isEqualTo(SceneKey.Bouncer)
+ val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
+ assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
+ .that(authMethodViewModel)
+ .isInstanceOf(PinBouncerViewModel::class.java)
+
+ val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ pinBouncerViewModel.onPinButtonClicked(digit)
+ }
+ pinBouncerViewModel.onAuthenticateButtonClicked()
+ runCurrent()
+ }
+
+ /** Changes device wakefulness state from asleep to awake, going through intermediary states. */
+ private fun TestScope.wakeUpDevice() {
+ val wakefulnessModel = keyguardRepository.wakefulness.value
+ assertWithMessage("Cannot wake up device as it's already awake!")
+ .that(wakefulnessModel.isStartingToWakeOrAwake())
+ .isFalse()
+
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_WAKE)
+ )
+ runCurrent()
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.AWAKE)
+ )
+ runCurrent()
+ }
+
+ /** Changes device wakefulness state from awake to asleep, going through intermediary states. */
+ private suspend fun TestScope.putDeviceToSleep(
+ instantlyLockDevice: Boolean = true,
+ ) {
+ val wakefulnessModel = keyguardRepository.wakefulness.value
+ assertWithMessage("Cannot put device to sleep as it's already asleep!")
+ .that(wakefulnessModel.isStartingToWakeOrAwake())
+ .isTrue()
+
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_SLEEP)
+ )
+ runCurrent()
+ keyguardRepository.setWakefulnessModel(
+ wakefulnessModel.copy(state = WakefulnessState.ASLEEP)
+ )
+ runCurrent()
+
+ if (instantlyLockDevice) {
+ lockDevice()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 0a93a7c..16cc924 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -28,9 +28,6 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -105,6 +102,40 @@
}
@Test
+ fun transitioningTo() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(underTest.desiredScene.value.key)
+ )
+ underTest.setTransitionState(transitionState)
+
+ val transitionTo by collectLastValue(underTest.transitioningTo)
+ assertThat(transitionTo).isNull()
+
+ underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+ assertThat(transitionTo).isNull()
+
+ val progress = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = underTest.desiredScene.value.key,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ )
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ progress.value = 0.5f
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ progress.value = 1f
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+ assertThat(transitionTo).isNull()
+ }
+
+ @Test
fun isVisible() =
testScope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
@@ -118,81 +149,6 @@
}
@Test
- fun finishedSceneTransitions() =
- testScope.runTest {
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(SceneKey.Lockscreen)
- )
- underTest.setTransitionState(transitionState)
- var transitionCount = 0
- val job = launch {
- underTest
- .finishedSceneTransitions(
- from = SceneKey.Shade,
- to = SceneKey.QuickSettings,
- )
- .collect { transitionCount++ }
- }
-
- assertThat(transitionCount).isEqualTo(0)
-
- underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = SceneKey.Shade,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
- runCurrent()
- assertThat(transitionCount).isEqualTo(0)
-
- underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Shade,
- toScene = SceneKey.QuickSettings,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
- runCurrent()
- assertThat(transitionCount).isEqualTo(1)
-
- underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.QuickSettings,
- toScene = SceneKey.Shade,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
- runCurrent()
- assertThat(transitionCount).isEqualTo(1)
-
- underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Shade,
- toScene = SceneKey.QuickSettings,
- progress = flowOf(0.5f),
- )
- runCurrent()
- underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason")
- transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings)
- runCurrent()
- assertThat(transitionCount).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
fun remoteUserInput() =
testScope.runTest {
val remoteUserInput by collectLastValue(underTest.remoteUserInput)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 45db7a0..951cadd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -21,7 +21,7 @@
import android.view.Display
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.model.SysUiState
import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -80,14 +81,13 @@
)
@Test
- fun hydrateVisibility_featureEnabled() =
+ fun hydrateVisibility() =
testScope.runTest {
val currentDesiredSceneKey by
collectLastValue(sceneInteractor.desiredScene.map { it.key })
val isVisible by collectLastValue(sceneInteractor.isVisible)
val transitionStateFlow =
prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = true,
initialSceneKey = SceneKey.Gone,
)
@@ -123,44 +123,10 @@
}
@Test
- fun hydrateVisibility_featureDisabled() =
- testScope.runTest {
- val currentDesiredSceneKey by
- collectLastValue(sceneInteractor.desiredScene.map { it.key })
- val isVisible by collectLastValue(sceneInteractor.isVisible)
- val transitionStateFlow =
- prepareState(
- isFeatureEnabled = false,
- isDeviceUnlocked = true,
- initialSceneKey = SceneKey.Gone,
- )
- assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
- assertThat(isVisible).isTrue()
-
- underTest.start()
-
- assertThat(isVisible).isTrue()
-
- sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
- transitionStateFlow.value =
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Gone,
- toScene = SceneKey.Shade,
- progress = flowOf(0.5f),
- )
- assertThat(isVisible).isTrue()
-
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
- transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
- assertThat(isVisible).isTrue()
- }
-
- @Test
- fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
+ fun switchToLockscreenWhenDeviceLocks() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = true,
initialSceneKey = SceneKey.Gone,
)
@@ -173,28 +139,10 @@
}
@Test
- fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
+ fun switchFromBouncerToGoneWhenDeviceUnlocked() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = false,
- isDeviceUnlocked = false,
- initialSceneKey = SceneKey.Gone,
- )
- assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
- underTest.start()
-
- authenticationRepository.setUnlocked(false)
-
- assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
- }
-
- @Test
- fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
- testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = false,
initialSceneKey = SceneKey.Bouncer,
)
@@ -207,28 +155,10 @@
}
@Test
- fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
+ fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = false,
- isDeviceUnlocked = false,
- initialSceneKey = SceneKey.Bouncer,
- )
- assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
- underTest.start()
-
- authenticationRepository.setUnlocked(true)
-
- assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
- }
-
- @Test
- fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
- testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = true,
isBypassEnabled = true,
initialSceneKey = SceneKey.Lockscreen,
)
@@ -241,11 +171,10 @@
}
@Test
- fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
+ fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
isBypassEnabled = false,
initialSceneKey = SceneKey.Lockscreen,
)
@@ -258,28 +187,10 @@
}
@Test
- fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
+ fun switchToLockscreenWhenDeviceSleepsLocked() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = false,
- isBypassEnabled = true,
- initialSceneKey = SceneKey.Lockscreen,
- )
- assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
- underTest.start()
-
- authenticationRepository.setUnlocked(true)
-
- assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
- }
-
- @Test
- fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
- testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = true,
isDeviceUnlocked = false,
initialSceneKey = SceneKey.Shade,
)
@@ -292,23 +203,6 @@
}
@Test
- fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
- testScope.runTest {
- val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
- prepareState(
- isFeatureEnabled = false,
- isDeviceUnlocked = false,
- initialSceneKey = SceneKey.Shade,
- )
- assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
- underTest.start()
-
- keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
-
- assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
- }
-
- @Test
fun hydrateSystemUiState() =
testScope.runTest {
val transitionStateFlow = prepareState()
@@ -339,11 +233,10 @@
}
@Test
- fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() =
+ fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
initialSceneKey = SceneKey.Lockscreen,
authenticationMethod = AuthenticationMethodModel.None,
)
@@ -356,11 +249,26 @@
}
@Test
- fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() =
+ fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = true,
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Swipe,
+ )
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ underTest.start()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() =
+ testScope.runTest {
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+ prepareState(
initialSceneKey = SceneKey.Lockscreen,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -373,30 +281,31 @@
}
@Test
- fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() =
+ fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
- isFeatureEnabled = false,
initialSceneKey = SceneKey.Lockscreen,
- authenticationMethod = AuthenticationMethodModel.None,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
+ authenticationRepository.setUnlocked(true)
+ runCurrent()
keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
- assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
private fun prepareState(
- isFeatureEnabled: Boolean = true,
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
initialSceneKey: SceneKey? = null,
authenticationMethod: AuthenticationMethodModel? = null,
): MutableStateFlow<ObservableTransitionState> {
- featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
+ featureFlags.set(Flags.SCENE_CONTAINER, true)
authenticationRepository.setUnlocked(isDeviceUnlocked)
keyguardRepository.setBypassEnabled(isBypassEnabled)
val transitionStateFlow =
@@ -410,7 +319,7 @@
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
}
authenticationMethod?.let {
- authenticationRepository.setAuthenticationMethod(authenticationMethod)
+ authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
authenticationRepository.setLockscreenEnabled(
authenticationMethod != AuthenticationMethodModel.None
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 7b3e89d..1edeeff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -95,7 +95,7 @@
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock private lateinit var shadeController: ShadeController
+ @Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -150,44 +150,45 @@
testScope = TestScope()
underTest =
NotificationShadeWindowViewController(
- lockscreenShadeTransitionController,
- FalsingCollectorFake(),
- sysuiStatusBarStateController,
- dockManager,
- notificationShadeDepthController,
- view,
- notificationPanelViewController,
- ShadeExpansionStateManager(),
- stackScrollLayoutController,
- statusBarKeyguardViewManager,
- statusBarWindowStateController,
- lockIconViewController,
- centralSurfaces,
- backActionInteractor,
- powerInteractor,
- notificationShadeWindowController,
- unfoldTransitionProgressProvider,
- keyguardUnlockAnimationController,
- notificationInsetsController,
- ambientState,
- pulsingGestureListener,
- mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- mock(KeyguardMessageAreaController.Factory::class.java),
- keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
- notificationExpansionRepository,
- featureFlags,
- FakeSystemClock(),
- BouncerMessageInteractor(
- FakeBouncerMessageRepository(),
- mock(BouncerMessageFactory::class.java),
- FakeUserRepository(),
- CountDownTimerUtil(),
- featureFlags
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog"))
+ lockscreenShadeTransitionController,
+ FalsingCollectorFake(),
+ sysuiStatusBarStateController,
+ dockManager,
+ notificationShadeDepthController,
+ view,
+ notificationPanelViewController,
+ ShadeExpansionStateManager(),
+ stackScrollLayoutController,
+ statusBarKeyguardViewManager,
+ statusBarWindowStateController,
+ lockIconViewController,
+ centralSurfaces,
+ backActionInteractor,
+ powerInteractor,
+ notificationShadeWindowController,
+ unfoldTransitionProgressProvider,
+ keyguardUnlockAnimationController,
+ notificationInsetsController,
+ ambientState,
+ shadeLogger,
+ pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory,
+ mock(KeyguardMessageAreaController.Factory::class.java),
+ keyguardTransitionInteractor,
+ primaryBouncerToGoneTransitionViewModel,
+ notificationExpansionRepository,
+ featureFlags,
+ FakeSystemClock(),
+ BouncerMessageInteractor(
+ FakeBouncerMessageRepository(),
+ mock(BouncerMessageFactory::class.java),
+ FakeUserRepository(),
+ CountDownTimerUtil(),
+ featureFlags
+ ),
+ BouncerLogger(logcatLogBuffer("BouncerLog"))
)
underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 5c3ce71..829184c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -105,6 +105,7 @@
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
+ @Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@@ -179,6 +180,7 @@
keyguardUnlockAnimationController,
notificationInsetsController,
ambientState,
+ shadeLogger,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 52e0c9c..6a14a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -59,6 +60,7 @@
@Mock private lateinit var shadeViewController: ShadeViewController
@Mock private lateinit var nswvc: NotificationShadeWindowViewController
@Mock private lateinit var display: Display
+ @Mock private lateinit var touchLog: LogBuffer
private lateinit var shadeController: ShadeControllerImpl
@@ -71,6 +73,7 @@
ShadeControllerImpl(
commandQueue,
FakeExecutor(FakeSystemClock()),
+ touchLog,
keyguardStateController,
statusBarStateController,
statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 2501f85..8f8b840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -138,19 +138,19 @@
@Before
fun setup() {
- whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
+ whenever<Clock>(view.requireViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
- whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+ whenever<TextView>(view.requireViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
- whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+ whenever<ShadeCarrierGroup>(view.requireViewById(R.id.carrier_group)).thenReturn(carrierGroup)
- whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+ whenever<BatteryMeterView>(view.requireViewById(R.id.batteryRemainingIcon))
.thenReturn(batteryMeterView)
- whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
- whenever<View>(view.findViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
+ whenever<StatusIconContainer>(view.requireViewById(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever<View>(view.requireViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
viewContext = Mockito.spy(context)
whenever(view.context).thenReturn(viewContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 7443097..69b9525 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -42,6 +42,7 @@
private val authenticationInteractor =
utils.authenticationInteractor(
repository = utils.authenticationRepository(),
+ sceneInteractor = sceneInteractor,
)
private val underTest =
@@ -76,6 +77,30 @@
}
@Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index b6da20f..280897d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -17,21 +17,19 @@
package com.android.systemui.statusbar;
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.app.ActivityManager;
import android.app.Notification;
-import android.app.PendingIntent;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -67,17 +65,15 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- protected static final int TEST_MINIMUM_DISPLAY_TIME = 200;
- protected static final int TEST_STICKY_DISPLAY_TIME = 1000;
- protected static final int TEST_AUTO_DISMISS_TIME = 500;
+ protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+ protected static final int TEST_AUTO_DISMISS_TIME = 600;
+ protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
// Number of notifications to use in tests requiring multiple notifications
private static final int TEST_NUM_NOTIFICATIONS = 4;
- protected static final int TEST_TIMEOUT_TIME = 15000;
+ protected static final int TEST_TIMEOUT_TIME = 2_000;
protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
- protected NotificationEntry mEntry;
protected Handler mTestHandler;
- private StatusBarNotification mSbn;
protected boolean mTimedOut = false;
@Mock protected ExpandableNotificationRow mRow;
@@ -88,8 +84,8 @@
private TestableAlertingNotificationManager(Handler handler) {
super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+ mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
}
@Override
@@ -114,7 +110,7 @@
return new TestableAlertingNotificationManager(mTestHandler);
}
- protected StatusBarNotification createNewSbn(int id, Notification n) {
+ protected StatusBarNotification createSbn(int id, Notification n) {
return new StatusBarNotification(
TEST_PACKAGE_NAME /* pkg */,
TEST_PACKAGE_NAME,
@@ -128,45 +124,53 @@
0 /* postTime */);
}
- protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
- return new StatusBarNotification(
- TEST_PACKAGE_NAME /* pkg */,
- TEST_PACKAGE_NAME,
- id,
- null /* tag */,
- TEST_UID,
- 0 /* initialPid */,
- n.build(),
- new UserHandle(ActivityManager.getCurrentUser()),
- null /* overrideGroupKey */,
- 0 /* postTime */);
+ protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+ return createSbn(id, n.build());
}
- protected StatusBarNotification createNewNotification(int id) {
- Notification.Builder n = new Notification.Builder(mContext, "")
+ protected StatusBarNotification createSbn(int id) {
+ final Notification.Builder b = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
- return createNewSbn(id, n);
+ return createSbn(id, b);
}
- protected StatusBarNotification createStickySbn(int id) {
- Notification stickyHun = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
- .build();
- stickyHun.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
- return createNewSbn(id, stickyHun);
+ protected NotificationEntry createEntry(int id, Notification n) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+ }
+
+ protected NotificationEntry createEntry(int id) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+ }
+
+ protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
+ boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
+ final Boolean[] wasAlerting = {null};
+ final Runnable checkAlerting =
+ () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
+
+ mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
+ mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ TestableLooper.get(this).processMessages(2);
+
+ assertFalse("Test timed out", mTimedOut);
+ if (shouldBeAlerting) {
+ assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
+ } else {
+ assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
+ }
+ assertFalse("Should not still be alerting after processing",
+ anm.isAlerting(entry.getKey()));
}
@Before
public void setUp() {
mTestHandler = Handler.createAsync(Looper.myLooper());
- mSbn = createNewNotification(0 /* id */);
- mEntry = new NotificationEntryBuilder()
- .setSbn(mSbn)
- .build();
- mEntry.setRow(mRow);
+
+ assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+ assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+ assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
}
@After
@@ -176,59 +180,64 @@
@Test
public void testShowNotification_addsEntry() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- alm.showNotification(mEntry);
+ alm.showNotification(entry);
- assertTrue(alm.isAlerting(mEntry.getKey()));
+ assertTrue(alm.isAlerting(entry.getKey()));
assertTrue(alm.hasNotifications());
- assertEquals(mEntry, alm.getEntry(mEntry.getKey()));
+ assertEquals(entry, alm.getEntry(entry.getKey()));
}
@Test
public void testShowNotification_autoDismisses() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- alm.showNotification(mEntry);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ alm.showNotification(entry);
- // Wait for remove runnable and then process it immediately
- TestableLooper.get(this).processMessages(1);
+ verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
+ "auto dismiss time");
- assertFalse("Test timed out", mTimedOut);
- assertFalse(alm.isAlerting(mEntry.getKey()));
+ assertFalse(alm.isAlerting(entry.getKey()));
}
@Test
public void testRemoveNotification_removeDeferred() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// Try to remove but defer, since the notification has not been shown long enough.
- alm.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+ final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+ false /* releaseImmediately */);
- assertTrue(alm.isAlerting(mEntry.getKey()));
+ assertFalse(removedImmediately);
+ assertTrue(alm.isAlerting(entry.getKey()));
}
@Test
public void testRemoveNotification_forceRemove() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// Remove forcibly with releaseImmediately = true.
- alm.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+ final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+ true /* releaseImmediately */);
- assertFalse(alm.isAlerting(mEntry.getKey()));
+ assertTrue(removedImmediately);
+ assertFalse(alm.isAlerting(entry.getKey()));
}
@Test
public void testReleaseAllImmediately() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
- StatusBarNotification sbn = createNewNotification(i);
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(sbn)
- .build();
+ final NotificationEntry entry = createEntry(i);
entry.setRow(mRow);
alm.showNotification(entry);
}
@@ -240,10 +249,12 @@
@Test
public void testCanRemoveImmediately_notShownLongEnough() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// The entry has just been added so we should not remove immediately.
- assertFalse(alm.canRemoveImmediately(mEntry.getKey()));
+ assertFalse(alm.canRemoveImmediately(entry.getKey()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
index bc32759..f2207af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
@@ -24,97 +24,53 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
import android.graphics.Color;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
-import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.appops.AppOpsController;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class AppOpsCoordinatorTest extends SysuiTestCase {
- private static final String TEST_PKG = "test_pkg";
- private static final int NOTIF_USER_ID = 0;
+public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
- @Mock private ForegroundServiceController mForegroundServiceController;
- @Mock private AppOpsController mAppOpsController;
+ private static final int NOTIF_USER_ID = 0;
@Mock private NotifPipeline mNotifPipeline;
private NotificationEntryBuilder mEntryBuilder;
- private AppOpsCoordinator mAppOpsCoordinator;
- private NotifFilter mForegroundFilter;
+ private ColorizedFgsCoordinator mColorizedFgsCoordinator;
private NotifSectioner mFgsSection;
- private FakeSystemClock mClock = new FakeSystemClock();
- private FakeExecutor mExecutor = new FakeExecutor(mClock);
-
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
- mAppOpsCoordinator =
- new AppOpsCoordinator(
- mForegroundServiceController,
- mAppOpsController,
- mExecutor);
+ mColorizedFgsCoordinator = new ColorizedFgsCoordinator();
mEntryBuilder = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID));
- mAppOpsCoordinator.attach(mNotifPipeline);
+ mColorizedFgsCoordinator.attach(mNotifPipeline);
- // capture filter
- ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
- mForegroundFilter = filterCaptor.getValue();
-
- mFgsSection = mAppOpsCoordinator.getSectioner();
- }
-
- @Test
- public void filterTest_disclosureUnnecessary() {
- NotificationEntry entry = mEntryBuilder.build();
- StatusBarNotification sbn = entry.getSbn();
-
- // GIVEN the notification is a disclosure notification
- when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
-
- // GIVEN the disclosure isn't needed for this user
- when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
- .thenReturn(false);
-
- // THEN filter out the notification
- assertTrue(mForegroundFilter.shouldFilterOut(entry, 0));
+ mFgsSection = mColorizedFgsCoordinator.getSectioner();
}
@Test
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 61da901..39b2948 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
@@ -115,6 +115,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.plugins.ActivityStarter;
@@ -441,6 +442,7 @@
mShadeController = spy(new ShadeControllerImpl(
mCommandQueue,
mMainExecutor,
+ mock(LogBuffer.class),
mKeyguardStateController,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 6155e3c..03d3854 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -274,6 +274,23 @@
}
@Test
+ fun orientationUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.orientation = Configuration.ORIENTATION_LANDSCAPE
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the orientation is updated
+ config.orientation = Configuration.ORIENTATION_PORTRAIT
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.orientationChanged).isTrue()
+ }
+
+
+ @Test
fun multipleUpdates_listenerNotifiedOfAll() {
val config = mContext.resources.configuration
config.densityDpi = 14
@@ -325,6 +342,7 @@
var themeChanged = false
var localeListChanged = false
var layoutDirectionChanged = false
+ var orientationChanged = false
override fun onConfigChanged(newConfig: Configuration?) {
changedConfig = newConfig
@@ -350,6 +368,9 @@
override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
layoutDirectionChanged = true
}
+ override fun onOrientationChanged(orientation: Int) {
+ orientationChanged = true
+ }
fun assertNoMethodsCalled() {
assertThat(densityOrFontScaleChanged).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 2f1e372..ec6286b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -75,6 +75,7 @@
private HeadsUpManagerPhone mHeadsUpManager;
private View mOperatorNameView;
private StatusBarStateController mStatusbarStateController;
+ private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
private KeyguardBypassController mBypassController;
private NotificationWakeUpCoordinator mWakeUpCoordinator;
private KeyguardStateController mKeyguardStateController;
@@ -95,6 +96,7 @@
mHeadsUpManager = mock(HeadsUpManagerPhone.class);
mOperatorNameView = new View(mContext);
mStatusbarStateController = mock(StatusBarStateController.class);
+ mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
mBypassController = mock(KeyguardBypassController.class);
mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
mKeyguardStateController = mock(KeyguardStateController.class);
@@ -105,6 +107,7 @@
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
+ mPhoneStatusBarTransitions,
mBypassController,
mWakeUpCoordinator,
mDarkIconDispatcher,
@@ -188,6 +191,7 @@
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
+ mPhoneStatusBarTransitions,
mBypassController,
mWakeUpCoordinator,
mDarkIconDispatcher,
@@ -283,4 +287,18 @@
/* delta = */ 0.001
);
}
+
+ @Test
+ public void onHeadsUpStateChanged_true_transitionsNotified() {
+ mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
+
+ verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
+ }
+
+ @Test
+ public void onHeadsUpStateChanged_false_transitionsNotified() {
+ mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
+
+ verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 72522ca..bb20d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -39,7 +39,6 @@
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -62,8 +61,6 @@
public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
- private HeadsUpManagerPhone mHeadsUpManager;
-
private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
logcatLogBuffer());
@Mock private GroupMembershipManager mGroupManager;
@@ -108,25 +105,8 @@
}
}
- @Override
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return mHeadsUpManager;
- }
-
- @Before
- @Override
- public void setUp() {
- AccessibilityManagerWrapper accessibilityMgr =
- mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
- when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
- .thenReturn(TEST_AUTO_DISMISS_TIME);
- when(mVSProvider.isReorderingAllowed()).thenReturn(true);
- mDependency.injectMockDependency(NotificationShadeWindowController.class);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.ambient_notification_extension_time, 500);
-
- super.setUp();
- mHeadsUpManager = new TestableHeadsUpManagerPhone(
+ private HeadsUpManagerPhone createHeadsUpManagerPhone() {
+ return new TestableHeadsUpManagerPhone(
mContext,
mHeadsUpManagerLogger,
mGroupManager,
@@ -141,6 +121,26 @@
);
}
+ @Override
+ protected AlertingNotificationManager createAlertingNotificationManager() {
+ return createHeadsUpManagerPhone();
+ }
+
+ @Before
+ @Override
+ public void setUp() {
+ final AccessibilityManagerWrapper accessibilityMgr =
+ mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
+ when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+ .thenReturn(TEST_AUTO_DISMISS_TIME);
+ when(mVSProvider.isReorderingAllowed()).thenReturn(true);
+ mDependency.injectMockDependency(NotificationShadeWindowController.class);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.ambient_notification_extension_time, 500);
+
+ super.setUp();
+ }
+
@After
@Override
public void tearDown() {
@@ -149,63 +149,67 @@
@Test
public void testSnooze() {
- mHeadsUpManager.showNotification(mEntry);
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- mHeadsUpManager.snooze();
+ hmp.showNotification(entry);
+ hmp.snooze();
- assertTrue(mHeadsUpManager.isSnoozed(mEntry.getSbn().getPackageName()));
+ assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
}
@Test
public void testSwipedOutNotification() {
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hmp.showNotification(entry);
+ hmp.addSwipedOutNotification(entry.getKey());
// Remove should succeed because the notification is swiped out
- mHeadsUpManager.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+ final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
+ /* releaseImmediately = */ false);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ assertTrue(removedImmediately);
+ assertFalse(hmp.isAlerting(entry.getKey()));
}
@Test
public void testCanRemoveImmediately_swipedOut() {
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hmp.showNotification(entry);
+ hmp.addSwipedOutNotification(entry.getKey());
// Notification is swiped so it can be immediately removed.
- assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+ assertTrue(hmp.canRemoveImmediately(entry.getKey()));
}
@Ignore("b/141538055")
@Test
public void testCanRemoveImmediately_notTopEntry() {
- NotificationEntry laterEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build();
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
+ final NotificationEntry laterEntry = createEntry(/* id = */ 1);
laterEntry.setRow(mRow);
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.showNotification(laterEntry);
+
+ hmp.showNotification(earlierEntry);
+ hmp.showNotification(laterEntry);
// Notification is "behind" a higher priority notification so we can remove it immediately.
- assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+ assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
}
@Test
public void testExtendHeadsUp() {
- mHeadsUpManager.showNotification(mEntry);
- Runnable pastNormalTimeRunnable =
- () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
- mTestHandler.postDelayed(pastNormalTimeRunnable,
- TEST_AUTO_DISMISS_TIME + mHeadsUpManager.mExtensionTime / 2);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- mHeadsUpManager.extendHeadsUp();
+ hmp.showNotification(entry);
+ hmp.extendHeadsUp();
- // Wait for normal time runnable and extended remove runnable and process them on arrival.
- TestableLooper.get(this).processMessages(2);
-
- assertFalse("Test timed out", mTimedOut);
- assertTrue("Pulse was not extended", mLivesPastNormalTime);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2;
+ verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time");
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
new file mode 100644
index 0000000..4af1b24
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 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.phone
+
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PhoneStatusBarTransitionsTest : SysuiTestCase() {
+
+ // PhoneStatusBarView does a lot of non-standard things when inflating, so just use mocks.
+ private val batteryView = mock<View>()
+ private val statusIcons = mock<View>()
+ private val startIcons = mock<View>()
+ private val statusBarView =
+ mock<PhoneStatusBarView>().apply {
+ whenever(this.context).thenReturn(mContext)
+ whenever(this.findViewById<View>(R.id.battery)).thenReturn(batteryView)
+ whenever(this.findViewById<View>(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever(this.findViewById<View>(R.id.status_bar_start_side_except_heads_up))
+ .thenReturn(startIcons)
+ }
+ private val backgroundView = mock<View>().apply { whenever(this.context).thenReturn(mContext) }
+
+ private val underTest: PhoneStatusBarTransitions by lazy {
+ PhoneStatusBarTransitions(statusBarView, backgroundView).also {
+ // The views' alphas will be set when PhoneStatusBarTransitions is created and we want
+ // to ignore those in the tests, so clear those verifications here.
+ reset(batteryView)
+ reset(statusIcons)
+ reset(startIcons)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ context.orCreateTestableResources.addOverride(
+ R.dimen.status_bar_icon_drawing_alpha,
+ RESOURCE_ALPHA,
+ )
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_batteryTranslucent() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ val alpha = batteryView.capturedAlpha()
+ assertThat(alpha).isGreaterThan(0)
+ assertThat(alpha).isLessThan(1)
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_statusIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_batteryTranslucent() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ val alpha = batteryView.capturedAlpha()
+ assertThat(alpha).isGreaterThan(0)
+ assertThat(alpha).isLessThan(1)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_statusIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_translucentMode_batteryIconShown() {
+ underTest.transitionTo(/* mode= */ MODE_TRANSLUCENT, /* animate= */ false)
+
+ assertThat(batteryView.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_semiTransparentMode_statusIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_transparentMode_startIconsShown() {
+ // Transparent is the default, so we need to switch to a different mode first
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.transitionTo(/* mode= */ MODE_TRANSPARENT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_batteryIconUsesResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(batteryView.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_statusIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_true_semiTransparentMode_startIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_true_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ /** Regression test for b/291173113. */
+ @Test
+ fun onHeadsUpStateChanged_true_lightsOutMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_semiTransparentMode_startIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_lightsOutMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ private fun View.capturedAlpha(): Float {
+ val captor = argumentCaptor<Float>()
+ verify(this).alpha = captor.capture()
+ return captor.value
+ }
+
+ private companion object {
+ const val RESOURCE_ALPHA = 0.34f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 9c52788..34c4ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -32,7 +32,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.ForegroundServiceNotificationListener;
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
@@ -94,7 +93,6 @@
mDependency.injectTestDependency(ShadeController.class, mShadeController);
mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
mDependency.injectMockDependency(NotificationShadeWindowController.class);
- mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
NotificationShadeWindowView notificationShadeWindowView =
mock(NotificationShadeWindowView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 14edf3d..e5bbead 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
+
import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -40,7 +42,6 @@
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
-import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -48,6 +49,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -63,16 +65,11 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
- private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
- private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
+ private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+ private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+ private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
- private HeadsUpManager mHeadsUpManager;
- private boolean mLivesPastNormalTime;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
- @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
- @Mock private NotificationEntry mEntry;
- @Mock private StatusBarNotification mSbn;
- @Mock private Notification mNotification;
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
@@ -83,27 +80,81 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger) {
super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+ mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+ mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
}
}
+ private HeadsUpManager createHeadsUpManager() {
+ return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
+ mUiEventLoggerFake);
+ }
+
@Override
protected AlertingNotificationManager createAlertingNotificationManager() {
- return mHeadsUpManager;
+ return createHeadsUpManager();
}
+ private NotificationEntry createStickyEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+ private NotificationEntry createStickyForSomeTimeEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+ private PendingIntent createFullScreenIntent() {
+ return PendingIntent.getActivity(
+ getContext(), 0, new Intent(getContext(), this.getClass()),
+ PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ }
+
+ private NotificationEntry createFullScreenIntentEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+
+ private void useAccessibilityTimeout(boolean use) {
+ if (use) {
+ doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
+ .getRecommendedTimeoutMillis(anyInt(), anyInt());
+ } else {
+ when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
+ i -> i.getArgument(0));
+ }
+ }
+
+
@Before
@Override
public void setUp() {
initMocks(this);
- when(mEntry.getSbn()).thenReturn(mSbn);
- when(mEntry.getKey()).thenReturn("entryKey");
- when(mSbn.getNotification()).thenReturn(mNotification);
super.setUp();
- mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
- mAccessibilityMgr, mUiEventLoggerFake);
+
+ assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+ assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+ assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+ TEST_TIMEOUT_TIME);
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+ TEST_TIMEOUT_TIME);
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+ TEST_A11Y_TIMEOUT_TIME);
}
@After
@@ -114,193 +165,327 @@
@Test
public void testHunRemovedLogging() {
- mAlertEntry.mEntry = mEntry;
- mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
- verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = mock(HeadsUpManager.HeadsUpEntry.class);
+ headsUpEntry.mEntry = notifEntry;
+
+ hum.onAlertEntryRemoved(headsUpEntry);
+
+ verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
- // Set up NotifEntry with FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
// Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
- mHeadsUpManager.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ hum.showNotification(notifEntry);
+
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.wasUnpinned = false;
- assertTrue(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
- // Set up NotifEntry with FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
// Add notifEntry to ANM mAlertEntries map and make it unpinned
- mHeadsUpManager.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ hum.showNotification(notifEntry);
+
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.wasUnpinned = true;
- assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_noFSI_false() {
- // Set up NotifEntry with no FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertFalse(hum.shouldHeadsUpBecomePinned(entry));
}
+
+ @Test
+ public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastJustAutoDismissMillis =
+ TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME;
+ verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss");
+ }
+
+
+ @Test
+ public void testShowNotification_autoDismissesWithDefaultTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_sticky_neverAutoDismisses() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastLongestAutoDismissMillis =
+ TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME;
+ final Boolean[] wasAlerting = {null};
+ final Runnable checkAlerting =
+ () -> wasAlerting[0] = hum.isAlerting(entry.getKey());
+ mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis);
+ TestableLooper.get(this).processMessages(1);
+
+ assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]);
+ assertTrue("Should still be alerting after processing",
+ hum.isAlerting(entry.getKey()));
+ }
+
+
@Test
public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
- doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
- .getRecommendedTimeoutMillis(anyInt(), anyInt());
- mHeadsUpManager.showNotification(mEntry);
- Runnable pastNormalTimeRunnable =
- () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
- mTestHandler.postDelayed(pastNormalTimeRunnable,
- (TEST_A11Y_AUTO_DISMISS_TIME + TEST_AUTO_DISMISS_TIME) / 2);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_A11Y_TIMEOUT_TIME);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(true);
- TestableLooper.get(this).processMessages(2);
+ hum.showNotification(entry);
- assertFalse("Test timed out", mTimedOut);
- assertTrue("Heads up should live long enough", mLivesPastNormalTime);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+ useAccessibilityTimeout(true);
+
+ hum.showNotification(entry);
+
+ final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout");
+ }
+
+
+ @Test
+ public void testRemoveNotification_beforeMinimumDisplayTime() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ // Try to remove but defer, since the notification has not been shown long enough.
+ final boolean removedImmediately = hum.removeNotification(
+ entry.getKey(), false /* releaseImmediately */);
+
+ assertFalse("HUN should not be removed before minimum display time", removedImmediately);
+ assertTrue("HUN should still be alerting before minimum display time",
+ hum.isAlerting(entry.getKey()));
+
+ final int pastMinimumDisplayTimeMillis =
+ (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis,
+ "minimum display time");
+ }
+
+
+ @Test
+ public void testRemoveNotification_afterMinimumDisplayTime() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ // After the minimum display time:
+ // 1. Check whether the notification is still alerting.
+ // 2. Try to remove it and check whether the remove succeeded.
+ // 3. Check whether it is still alerting after trying to remove it.
+ final Boolean[] livedPastMinimumDisplayTime = {null};
+ final Boolean[] removedAfterMinimumDisplayTime = {null};
+ final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null};
+ final Runnable pastMinimumDisplayTimeRunnable = () -> {
+ livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+ removedAfterMinimumDisplayTime[0] = hum.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ false);
+ livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+ };
+ final int pastMinimumDisplayTimeMillis =
+ (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+ mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis);
+ // Wait until the minimum display time has passed before attempting removal.
+ TestableLooper.get(this).processMessages(1);
+
+ assertTrue("HUN should live past minimum display time",
+ livedPastMinimumDisplayTime[0]);
+ assertTrue("HUN should be removed immediately past minimum display time",
+ removedAfterMinimumDisplayTime[0]);
+ assertFalse("HUN should not live after being removed past minimum display time",
+ livedPastRemoveAfterMinimumDisplayTime[0]);
+ assertFalse(hum.isAlerting(entry.getKey()));
+ }
+
+
+ @Test
+ public void testRemoveNotification_releaseImmediately() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hum.showNotification(entry);
+
+ // Remove forcibly with releaseImmediately = true.
+ final boolean removedImmediately = hum.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ true);
+
+ assertTrue(removedImmediately);
+ assertFalse(hum.isAlerting(entry.getKey()));
}
@Test
public void testIsSticky_rowPinnedAndExpanded_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
-
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
when(mRow.isPinned()).thenReturn(true);
notifEntry.setRow(mRow);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.setExpanded(true);
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testIsSticky_remoteInputActive_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.remoteInputActive = true;
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testIsSticky_hasFullScreenIntent_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
+
@Test
- public void testIsSticky_stickyAndNotDemoted_true() {
- NotificationEntry alertEntry = new NotificationEntryBuilder()
- .setSbn(createStickySbn(0))
- .build();
+ public void testIsSticky_stickyForSomeTime_false() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(alertEntry);
+ hum.showNotification(entry);
- assertTrue(mHeadsUpManager.isSticky(alertEntry.getKey()));
+ assertFalse(hum.isSticky(entry.getKey()));
}
+
@Test
public void testIsSticky_false() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.setExpanded(false);
headsUpEntry.remoteInputActive = false;
- assertFalse(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertFalse(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testCompareTo_withNullEntries() {
- NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- mHeadsUpManager.showNotification(alertEntry);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- assertThat(mHeadsUpManager.compare(alertEntry, null)).isLessThan(0);
- assertThat(mHeadsUpManager.compare(null, alertEntry)).isGreaterThan(0);
- assertThat(mHeadsUpManager.compare(null, null)).isEqualTo(0);
+ hum.showNotification(alertEntry);
+
+ assertThat(hum.compare(alertEntry, null)).isLessThan(0);
+ assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
+ assertThat(hum.compare(null, null)).isEqualTo(0);
}
@Test
public void testCompareTo_withNonAlertEntries() {
- NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag("nae1").build();
- NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag("nae2").build();
- NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- mHeadsUpManager.showNotification(alertEntry);
+ final HeadsUpManager hum = createHeadsUpManager();
- assertThat(mHeadsUpManager.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
- assertThat(mHeadsUpManager.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
- assertThat(mHeadsUpManager.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
+ final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
+ "nae1").build();
+ final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
+ "nae2").build();
+ final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+ hum.showNotification(alertEntry);
+
+ assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
+ assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
+ assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
}
@Test
public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
- HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+ final HeadsUpManager hum = createHeadsUpManager();
+
+ final HeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
ongoingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
+ .setSbn(createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setCategory(Notification.CATEGORY_CALL)
.setOngoing(true)))
.build());
- HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
- activeRemoteInput.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build());
+ final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+ activeRemoteInput.setEntry(createEntry(/* id = */ 1));
activeRemoteInput.remoteInputActive = true;
assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -309,20 +494,20 @@
@Test
public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
- HeadsUpManager.HeadsUpEntry incomingCall = mHeadsUpManager.new HeadsUpEntry();
- Person person = new Person.Builder().setName("person").build();
- PendingIntent intent = mock(PendingIntent.class);
+ final HeadsUpManager hum = createHeadsUpManager();
+
+ final HeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry();
+ final Person person = new Person.Builder().setName("person").build();
+ final PendingIntent intent = mock(PendingIntent.class);
incomingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
+ .setSbn(createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setStyle(Notification.CallStyle
.forIncomingCall(person, intent, intent))))
.build());
- HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
- activeRemoteInput.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build());
+ final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+ activeRemoteInput.setEntry(createEntry(/* id = */ 1));
activeRemoteInput.remoteInputActive = true;
assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -331,22 +516,18 @@
@Test
public void testPinEntry_logsPeek() {
- // Needs full screen intent in order to be pinned
- final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
+ final HeadsUpManager hum = createHeadsUpManager();
- HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
- entryToPin.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
- new Notification.Builder(mContext, "")
- .setFullScreenIntent(fullScreenIntent, true)))
- .build());
+ // Needs full screen intent in order to be pinned
+ final HeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
+ entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+
// Note: the standard way to show a notification would be calling showNotification rather
// than onAlertEntryAdded. However, in practice showNotification in effect adds
// the notification and then updates it; in order to not log twice, the entry needs
// to have a functional ExpandableNotificationRow that can keep track of whether it's
// pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
- mHeadsUpManager.onAlertEntryAdded(entryToPin);
+ hum.onAlertEntryAdded(entryToPin);
assertEquals(1, mUiEventLoggerFake.numLogs());
assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
@@ -355,14 +536,15 @@
@Test
public void testSetUserActionMayIndirectlyRemove() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
- assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ hum.showNotification(notifEntry);
- mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
- assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
+
+ hum.setUserActionMayIndirectlyRemove(notifEntry);
+
+ assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 5cabcd4..cae892f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -36,6 +36,7 @@
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import dagger.Lazy;
@@ -67,6 +68,8 @@
private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@Mock
private KeyguardUpdateMonitorLogger mLogger;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
@@ -80,7 +83,8 @@
mLockPatternUtils,
mKeyguardUnlockAnimationControllerLazy,
mLogger,
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags);
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 0829f31..dd45331 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -18,9 +18,11 @@
import android.content.pm.UserInfo
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
import com.android.systemui.authentication.data.repository.AuthenticationRepository
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -123,6 +125,7 @@
repository: SceneContainerRepository = fakeSceneContainerRepository()
): SceneInteractor {
return SceneInteractor(
+ applicationScope = applicationScope(),
repository = repository,
logger = mock(),
)
@@ -201,5 +204,18 @@
RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE),
RemoteUserInput(10f, 40f, RemoteUserInputAction.UP),
)
+
+ fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
+ return when (this) {
+ DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
+ DomainLayerAuthenticationMethodModel.Swipe ->
+ DataLayerAuthenticationMethodModel.None
+ DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin
+ DomainLayerAuthenticationMethodModel.Password ->
+ DataLayerAuthenticationMethodModel.Password
+ DomainLayerAuthenticationMethodModel.Pattern ->
+ DataLayerAuthenticationMethodModel.Pattern
+ }
+ }
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 70aff05..d6702b7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -134,6 +134,7 @@
import android.service.autofill.CompositeUserData;
import android.service.autofill.Dataset;
import android.service.autofill.Dataset.DatasetEligibleReason;
+import android.service.autofill.Field;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
import android.service.autofill.FieldClassificationUserData;
@@ -189,6 +190,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -1780,11 +1782,11 @@
*/
private static class DatasetComputationContainer {
// List of all autofill ids that have a corresponding datasets
- Set<AutofillId> mAutofillIds = new ArraySet<>();
+ Set<AutofillId> mAutofillIds = new LinkedHashSet<>();
// Set of datasets. Kept separately, to be able to be used directly for composing
// FillResponse.
Set<Dataset> mDatasets = new LinkedHashSet<>();
- ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+ Map<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new LinkedHashMap<>();
public String toString() {
final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
@@ -1822,7 +1824,7 @@
// for now to keep safe. TODO(b/266379948): Revisit this logic.
Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id);
- Set<Dataset> copyDatasets = new ArraySet<>(datasets);
+ Set<Dataset> copyDatasets = new LinkedHashSet<>(datasets);
c1.mAutofillIds.add(id);
c1.mAutofillIdToDatasetMap.put(id, copyDatasets);
c1.mDatasets.addAll(copyDatasets);
@@ -1838,6 +1840,13 @@
}
}
+ /**
+ * Computes datasets that are eligible to be shown based on provider detections.
+ * Datasets are populated in the provided container for them to be later merged with the
+ * PCC eligible datasets based on preference strategy.
+ * @param response
+ * @param container
+ */
private void computeDatasetsForProviderAndUpdateContainer(
FillResponse response, DatasetComputationContainer container) {
@DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN;
@@ -1849,9 +1858,9 @@
}
List<Dataset> datasets = response.getDatasets();
if (datasets == null) return;
- ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
+ Map<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new LinkedHashMap<>();
Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
- Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+ Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
for (Dataset dataset : response.getDatasets()) {
if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
@DatasetEligibleReason int pickReason = globalPickReason;
@@ -1927,7 +1936,7 @@
eligibleAutofillIds.add(id);
Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id);
if (datasetForIds == null) {
- datasetForIds = new ArraySet<>();
+ datasetForIds = new LinkedHashSet<>();
}
datasetForIds.add(dataset);
autofillIdToDatasetMap.put(id, datasetForIds);
@@ -1938,23 +1947,30 @@
container.mAutofillIds = eligibleAutofillIds;
}
+ /**
+ * Computes datasets that are eligible to be shown based on PCC detections.
+ * Datasets are populated in the provided container for them to be later merged with the
+ * provider eligible datasets based on preference strategy.
+ * @param response
+ * @param container
+ */
private void computeDatasetsForPccAndUpdateContainer(
FillResponse response, DatasetComputationContainer container) {
List<Dataset> datasets = response.getDatasets();
if (datasets == null) return;
synchronized (mLock) {
- ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
+ Map<String, Set<AutofillId>> hintsToAutofillIdMap =
mClassificationState.mHintsToAutofillIdMap;
// TODO(266379948): Handle group hints too.
- ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
+ Map<String, Set<AutofillId>> groupHintsToAutofillIdMap =
mClassificationState.mGroupHintsToAutofillIdMap;
- ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>();
+ Map<AutofillId, Set<Dataset>> map = new LinkedHashMap<>();
Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
- Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+ Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
for (int i = 0; i < datasets.size(); i++) {
@@ -1970,13 +1986,35 @@
ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
- Set<AutofillId> datasetAutofillIds = new ArraySet<>();
+ Set<AutofillId> datasetAutofillIds = new LinkedHashSet<>();
+
+ boolean isDatasetAvailable = false;
+ Set<AutofillId> additionalDatasetAutofillIds = new LinkedHashSet<>();
+ Set<AutofillId> additionalEligibleAutofillIds = new LinkedHashSet<>();
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
if (dataset.getAutofillDatatypes().get(j) == null) {
+ // TODO : revisit pickReason logic
if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) {
pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
}
+ // Check if the autofill id at this index is detected by PCC.
+ // If not, add that id here, otherwise, we can have duplicates when later
+ // merging with provider datasets.
+ // Howover, this doesn't make datasetAvailable for PCC on its own.
+ // For that, there has to be a datatype detected by PCC, and the dataset
+ // for that datatype provided by the provider.
+ AutofillId autofillId = dataset.getFieldIds().get(j);
+ if (!mClassificationState.mClassificationCombinedHintsMap
+ .containsKey(autofillId)) {
+ additionalEligibleAutofillIds.add(autofillId);
+ additionalDatasetAutofillIds.add(autofillId);
+ // For each of the field, copy over values.
+ copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+ fieldPresentations, fieldDialogPresentations,
+ fieldInlinePresentations, fieldInlineTooltipPresentations,
+ fieldFilters);
+ }
continue;
}
String hint = dataset.getAutofillDatatypes().get(j);
@@ -1984,22 +2022,18 @@
if (hintsToAutofillIdMap.containsKey(hint)) {
ArrayList<AutofillId> tempIds =
new ArrayList<>(hintsToAutofillIdMap.get(hint));
-
+ if (tempIds.isEmpty()) {
+ continue;
+ }
+ isDatasetAvailable = true;
for (AutofillId autofillId : tempIds) {
eligibleAutofillIds.add(autofillId);
datasetAutofillIds.add(autofillId);
// For each of the field, copy over values.
- fieldIds.add(autofillId);
- fieldValues.add(dataset.getFieldValues().get(j));
- // TODO(b/266379948): might need to make it more efficient by not
- // copying over value if it didn't exist. This would require creating
- // a getter for the presentations arraylist.
- fieldPresentations.add(dataset.getFieldPresentation(j));
- fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
- fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
- fieldInlineTooltipPresentations.add(
- dataset.getFieldInlineTooltipPresentation(j));
- fieldFilters.add(dataset.getFilter(j));
+ copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+ fieldPresentations, fieldDialogPresentations,
+ fieldInlinePresentations, fieldInlineTooltipPresentations,
+ fieldFilters);
}
}
// TODO(b/266379948): handle the case:
@@ -2008,34 +2042,38 @@
// TODO(b/266379948): also handle the case where there could be more types in
// the dataset, provided by the provider, however, they aren't applicable.
}
- Dataset newDataset =
- new Dataset(
- fieldIds,
- fieldValues,
- fieldPresentations,
- fieldDialogPresentations,
- fieldInlinePresentations,
- fieldInlineTooltipPresentations,
- fieldFilters,
- new ArrayList<>(),
- dataset.getFieldContent(),
- null,
- null,
- null,
- null,
- dataset.getId(),
- dataset.getAuthentication());
- newDataset.setEligibleReasonReason(pickReason);
- eligibleDatasets.add(newDataset);
- Set<Dataset> newDatasets;
- for (AutofillId autofillId : datasetAutofillIds) {
- if (map.containsKey(autofillId)) {
- newDatasets = map.get(autofillId);
- } else {
- newDatasets = new ArraySet<>();
+ if (isDatasetAvailable) {
+ datasetAutofillIds.addAll(additionalDatasetAutofillIds);
+ eligibleAutofillIds.addAll(additionalEligibleAutofillIds);
+ Dataset newDataset =
+ new Dataset(
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
+ fieldFilters,
+ new ArrayList<>(),
+ dataset.getFieldContent(),
+ null,
+ null,
+ null,
+ null,
+ dataset.getId(),
+ dataset.getAuthentication());
+ newDataset.setEligibleReasonReason(pickReason);
+ eligibleDatasets.add(newDataset);
+ Set<Dataset> newDatasets;
+ for (AutofillId autofillId : datasetAutofillIds) {
+ if (map.containsKey(autofillId)) {
+ newDatasets = map.get(autofillId);
+ } else {
+ newDatasets = new LinkedHashSet<>();
+ }
+ newDatasets.add(newDataset);
+ map.put(autofillId, newDatasets);
}
- newDatasets.add(newDataset);
- map.put(autofillId, newDatasets);
}
}
container.mAutofillIds = eligibleAutofillIds;
@@ -2044,6 +2082,31 @@
}
}
+ private void copyFieldsFromDataset(
+ Dataset dataset,
+ int index,
+ AutofillId autofillId,
+ ArrayList<AutofillId> fieldIds,
+ ArrayList<AutofillValue> fieldValues,
+ ArrayList<RemoteViews> fieldPresentations,
+ ArrayList<RemoteViews> fieldDialogPresentations,
+ ArrayList<InlinePresentation> fieldInlinePresentations,
+ ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
+ ArrayList<Dataset.DatasetFieldFilter> fieldFilters) {
+ // copy over values
+ fieldIds.add(autofillId);
+ fieldValues.add(dataset.getFieldValues().get(index));
+ // TODO(b/266379948): might need to make it more efficient by not
+ // copying over value if it didn't exist. This would require creating
+ // a getter for the presentations arraylist.
+ fieldPresentations.add(dataset.getFieldPresentation(index));
+ fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index));
+ fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index));
+ fieldInlineTooltipPresentations.add(
+ dataset.getFieldInlineTooltipPresentation(index));
+ fieldFilters.add(dataset.getFilter(index));
+ }
+
// FillServiceCallbacks
@Override
@SuppressWarnings("GuardedBy")
@@ -2577,10 +2640,7 @@
if (sDebug) Slog.d(TAG, "Updating client state from auth dataset");
mClientState = newClientState;
}
- Dataset dataset = (Dataset) result;
- FillResponse temp = new FillResponse.Builder().addDataset(dataset).build();
- temp = getEffectiveFillResponse(temp);
- dataset = temp.getDatasets().get(0);
+ Dataset dataset = getEffectiveDatasetForAuthentication((Dataset) result);
final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
authenticatedResponse.getDatasets().set(datasetIdx, dataset);
@@ -2606,6 +2666,39 @@
}
}
+ Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) {
+ FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build();
+ response = getEffectiveFillResponse(response);
+ if (DBG) {
+ Slog.d(TAG, "DBG: authenticated effective response: " + response);
+ }
+ if (response == null || response.getDatasets().size() == 0) {
+ Log.wtf(TAG, "No datasets in fill response on authentication. response = "
+ + (response == null ? "null" : response.toString()));
+ return authenticatedDataset;
+ }
+ List<Dataset> datasets = response.getDatasets();
+ Dataset result = response.getDatasets().get(0);
+ if (datasets.size() > 1) {
+ Dataset.Builder builder = new Dataset.Builder();
+ for (Dataset dataset : datasets) {
+ if (!dataset.getFieldIds().isEmpty()) {
+ for (int i = 0; i < dataset.getFieldIds().size(); i++) {
+ builder.setField(dataset.getFieldIds().get(i),
+ new Field.Builder().setValue(dataset.getFieldValues().get(i))
+ .build());
+ }
+ }
+ }
+ result = builder.setId(authenticatedDataset.getId()).build();
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "DBG: authenticated effective dataset after auth: " + result);
+ }
+ return result;
+ }
+
/**
* Returns whether the dataset returned from the authentication result is ephemeral or not.
* See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 992c18a..66ea4d0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2501,12 +2501,12 @@
FGS_STOP_REASON_STOP_FOREGROUND,
FGS_TYPE_POLICY_CHECK_UNKNOWN);
- // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
- // earlier.
- r.foregroundServiceType = 0;
synchronized (mFGSLogger) {
mFGSLogger.logForegroundServiceStop(r.appInfo.uid, r);
}
+ // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
+ // earlier.
+ r.foregroundServiceType = 0;
r.mFgsNotificationWasDeferred = false;
signalForegroundServiceObserversLocked(r);
resetFgsRestrictionLocked(r);
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 7482e64..182205a 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,6 +30,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -161,6 +162,12 @@
WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+ SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
+
+ sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 786e1cc..f6859d1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -141,6 +141,10 @@
// grab the appropriate types
final IntArray apiTypes =
convertFgsTypeToApiTypes(record.foregroundServiceType);
+ if (apiTypes.size() == 0) {
+ Slog.w(TAG, "Foreground service start for UID: "
+ + uid + " does not have any types");
+ }
// now we need to iterate through the types
// and insert the new record as needed
final IntArray apiTypesFound = new IntArray();
@@ -201,6 +205,9 @@
// and also clean up the start calls stack by UID
final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
final UidState uidState = mUids.get(uid);
+ if (apiTypes.size() == 0) {
+ Slog.w(TAG, "FGS stop call for: " + uid + " has no types!");
+ }
if (uidState == null) {
Slog.w(TAG, "FGS stop call being logged with no start call for UID for UID "
+ uid
@@ -460,16 +467,17 @@
public void logFgsApiEvent(ServiceRecord r, int fgsState,
@FgsApiState int apiState,
@ForegroundServiceApiType int apiType, long timestamp) {
- long apiDurationBeforeFgsStart = r.createRealTime - timestamp;
- long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+ long apiDurationBeforeFgsStart = 0;
+ long apiDurationAfterFgsEnd = 0;
UidState uidState = mUids.get(r.appInfo.uid);
- if (uidState != null) {
- if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
- apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
- }
- if (uidState.mLastFgsTimeStamp.contains(apiType)) {
- apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
- }
+ if (uidState == null) {
+ return;
+ }
+ if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
+ apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
+ }
+ if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+ apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
}
final int[] apiTypes = new int[1];
apiTypes[0] = apiType;
@@ -525,10 +533,11 @@
@ForegroundServiceApiType int apiType, long timestamp) {
long apiDurationAfterFgsEnd = 0;
UidState uidState = mUids.get(uid);
- if (uidState != null) {
- if (uidState.mLastFgsTimeStamp.contains(apiType)) {
- apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
- }
+ if (uidState == null) {
+ return;
+ }
+ if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+ apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
}
final int[] apiTypes = new int[1];
apiTypes[0] = apiType;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index d8266ec..9f9e2eb 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -435,7 +435,7 @@
// LE Audio it stays the same and we must trigger the proper stream volume alignment, if
// LE Audio communication device is activated after the audio system has already switched to
// MODE_IN_CALL mode.
- if (isBluetoothLeAudioRequested()) {
+ if (isBluetoothLeAudioRequested() && device != null) {
final int streamType = mAudioService.getBluetoothContextualVolumeStream();
final int leAudioVolIndex = getVssVolumeForDevice(streamType, device.getInternalType());
final int leAudioMaxVolIndex = getMaxVssVolumeForStream(streamType);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5a92cb4..13e3fc7 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -2070,12 +2070,18 @@
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceAvailable(
AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
- final String address = btInfo.mDevice.getAddress();
- final String name = BtHelper.getName(btInfo.mDevice);
final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
final int device = btInfo.mAudioSystemDevice;
if (device != AudioSystem.DEVICE_NONE) {
+ final String address = btInfo.mDevice.getAddress();
+ String name = BtHelper.getName(btInfo.mDevice);
+
+ // The BT Stack does not provide a name for LE Broadcast devices
+ if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) {
+ name = "Broadcast";
+ }
+
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
*/
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 626502e..898a3c4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1546,6 +1546,7 @@
if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) {
mDisplayWindowPolicyControllers.put(
displayId, Pair.create(virtualDevice, dwpc));
+ Slog.d(TAG, "Virtual Display: successfully created virtual display");
}
}
@@ -1592,19 +1593,25 @@
if (!getProjectionService().setContentRecordingSession(session, projection)) {
// Unable to start mirroring, so release VirtualDisplay. Projection service
// handles stopping the projection.
+ Slog.w(TAG, "Content Recording: failed to start mirroring - "
+ + "releasing virtual display " + displayId);
releaseVirtualDisplayInternal(callback.asBinder());
return Display.INVALID_DISPLAY;
} else if (projection != null) {
// Indicate that this projection has been used to record, and can't be used
// again.
+ Slog.d(TAG, "Content Recording: notifying MediaProjection of successful"
+ + " VirtualDisplay creation.");
projection.notifyVirtualDisplayCreated(displayId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to tell MediaProjectionManagerService to set the "
+ "content recording session", e);
+ return displayId;
}
+ Slog.d(TAG, "Virtual Display: successfully set up virtual display "
+ + displayId);
}
-
return displayId;
} finally {
Binder.restoreCallingIdentity(secondToken);
@@ -1628,10 +1635,13 @@
return -1;
}
+
+ Slog.d(TAG, "Virtual Display: creating DisplayDevice with VirtualDisplayAdapter");
DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
callback, projection, callingUid, packageName, surface, flags,
virtualDisplayConfig);
if (device == null) {
+ Slog.w(TAG, "Virtual Display: VirtualDisplayAdapter failed to create DisplayDevice");
return -1;
}
@@ -1709,6 +1719,7 @@
DisplayDevice device =
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+ Slog.d(TAG, "Virtual Display: Display Device released");
if (device != null) {
// TODO: multi-display - handle virtual displays the same as other display adapters.
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 4f7a2ba..9f480b6 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -141,9 +141,12 @@
try {
if (projection != null) {
projection.registerCallback(mediaProjectionCallback);
+ Slog.d(TAG, "Virtual Display: registered media projection callback for new "
+ + "VirtualDisplayDevice");
}
appToken.linkToDeath(device, 0);
} catch (RemoteException ex) {
+ Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
mVirtualDisplayDevices.remove(appToken);
device.destroyLocked(false);
return null;
@@ -439,6 +442,7 @@
}
public void stopLocked() {
+ Slog.d(TAG, "Virtual Display: stopping device " + mName);
setSurfaceLocked(null);
mStopped = true;
}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 497ed03..fee54f5 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -28,6 +28,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.DeadObjectException;
import android.os.Handler;
import android.os.ILogd;
import android.os.Looper;
@@ -518,7 +519,15 @@
Slog.d(TAG, "Approving log access: " + request);
}
try {
- getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+ try {
+ getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+ } catch (DeadObjectException e) {
+ // This can happen if logd restarts, so force getting a new connection
+ // to logd and try once more.
+ Slog.w(TAG, "Logd connection no longer valid while approving, trying once more.");
+ mLogdService = null;
+ getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+ }
Integer activeCount = mActiveLogAccessCount.getOrDefault(client, 0);
mActiveLogAccessCount.put(client, activeCount + 1);
} catch (RemoteException e) {
@@ -531,7 +540,15 @@
Slog.d(TAG, "Declining log access: " + request);
}
try {
- getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+ try {
+ getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+ } catch (DeadObjectException e) {
+ // This can happen if logd restarts, so force getting a new connection
+ // to logd and try once more.
+ Slog.w(TAG, "Logd connection no longer valid while declining, trying once more.");
+ mLogdService = null;
+ getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Fails to call remote functions", e);
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index fb3f0b3..802a7f2 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -225,6 +225,7 @@
mMediaRouter.rebindAsUser(to.getUserIdentifier());
synchronized (mLock) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection due to user switching");
mProjectionGrant.stop();
}
}
@@ -260,6 +261,8 @@
}
synchronized (mLock) {
+ Slog.d(TAG,
+ "Content Recording: Stopped MediaProjection due to foreground service change");
if (mProjectionGrant != null) {
mProjectionGrant.stop();
}
@@ -268,6 +271,8 @@
private void startProjectionLocked(final MediaProjection projection) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection to start new "
+ + "incoming projection");
mProjectionGrant.stop();
}
if (mMediaRouteInfo != null) {
@@ -279,6 +284,8 @@
}
private void stopProjectionLocked(final MediaProjection projection) {
+ Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
+ + "dispatching stop to callbacks");
mProjectionToken = null;
mProjectionGrant = null;
dispatchStop(projection);
@@ -351,6 +358,13 @@
if (!setSessionSucceeded) {
// Unable to start mirroring, so tear down this projection.
if (mProjectionGrant != null) {
+ String projectionType = incomingSession != null
+ ? ContentRecordingSession.recordContentToString(
+ incomingSession.getContentToRecord()) : "none";
+ Slog.w(TAG, "Content Recording: Stopped MediaProjection due to failing to set "
+ + "ContentRecordingSession - id= "
+ + mProjectionGrant.getVirtualDisplayId() + "type=" + projectionType);
+
mProjectionGrant.stop();
}
return false;
@@ -464,6 +478,9 @@
// The grant may now be null if setting the session failed.
if (mProjectionGrant != null) {
// Always stop the projection.
+ Slog.w(TAG, "Content Recording: Stopped MediaProjection due to user "
+ + "consent result of CANCEL - "
+ + "id= " + mProjectionGrant.getVirtualDisplayId());
mProjectionGrant.stop();
}
break;
@@ -672,6 +689,7 @@
try {
synchronized (mLock) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopping active projection");
mProjectionGrant.stop();
}
}
@@ -882,6 +900,10 @@
MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
}
+ int getVirtualDisplayId() {
+ return mVirtualDisplayId;
+ }
+
@Override // Binder call
public boolean canProjectVideo() {
return mType == MediaProjectionManager.TYPE_MIRRORING ||
@@ -958,12 +980,11 @@
registerCallback(mCallback);
try {
mToken = callback.asBinder();
- mDeathEater = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- mCallbackDelegate.remove(callback);
- stop();
- }
+ mDeathEater = () -> {
+ Slog.d(TAG, "Content Recording: MediaProjection stopped by Binder death - "
+ + "id= " + mVirtualDisplayId);
+ mCallbackDelegate.remove(callback);
+ stop();
};
mToken.linkToDeath(mDeathEater, 0);
} catch (RemoteException e) {
@@ -1033,6 +1054,9 @@
Binder.restoreCallingIdentity(token);
}
}
+ Slog.d(TAG, "Content Recording: handling stopping this projection token"
+ + " createTime= " + mCreateTimeMs
+ + " countStarts= " + mCountStarts);
stopProjectionLocked(this);
mToken.unlinkToDeath(mDeathEater, 0);
mToken = null;
@@ -1158,6 +1182,8 @@
if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
mMediaRouteInfo = info;
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection due to "
+ + "route type of REMOTE_DISPLAY not selected");
mProjectionGrant.stop();
}
}
@@ -1329,7 +1355,7 @@
try {
mCallback.onStart(mInfo);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify media projection has stopped", e);
+ Slog.w(TAG, "Failed to notify media projection has started", e);
}
}
}
@@ -1403,7 +1429,8 @@
return "TYPE_MIRRORING";
case MediaProjectionManager.TYPE_PRESENTATION:
return "TYPE_PRESENTATION";
+ default:
+ return Integer.toString(type);
}
- return Integer.toString(type);
}
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bb1092..3f799dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2608,7 +2608,12 @@
for (NotificationChannel channel : r.channels.values()) {
if (!channel.isSoundRestored()) {
Uri uri = channel.getSound();
- Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
+ Uri restoredUri =
+ channel.restoreSoundUri(
+ mContext,
+ uri,
+ true,
+ channel.getAudioAttributes().getUsage());
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
restoredUri)) {
Log.w(TAG,
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index a622d07..bb6e11d 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -90,7 +90,7 @@
]
}
],
- "presubmit-large": [
+ "postsubmit": [
{
"name": "CtsContentTestCases",
"options": [
@@ -104,9 +104,7 @@
"include-filter": "android.content.pm.cts"
}
]
- }
- ],
- "postsubmit": [
+ },
{
"name": "CtsPermissionTestCases",
"options": [
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 4ce21bd..2eceecc 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -33,6 +33,7 @@
import com.android.internal.R;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Consumer;
@@ -224,7 +225,15 @@
* operation directly to avoid waiting until timeout.
*/
void updateTargetWindows() {
- if (mTransitionOp == OP_LEGACY || !mIsStartTransactionCommitted) return;
+ if (mTransitionOp == OP_LEGACY) return;
+ if (!mIsStartTransactionCommitted) {
+ if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+ && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
+ Slog.d(TAG, "Cancel for no change");
+ mDisplayContent.finishAsyncRotationIfPossible();
+ }
+ return;
+ }
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
final Operation op = mTargetWindowTokens.valueAt(i);
if (op.mIsCompletionPending || op.mAction == Operation.ACTION_SEAMLESS) {
@@ -608,6 +617,16 @@
return op.mAction != Operation.ACTION_SEAMLESS;
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "AsyncRotationController");
+ prefix += " ";
+ pw.println(prefix + "mTransitionOp=" + mTransitionOp);
+ pw.println(prefix + "mIsStartTransactionCommitted=" + mIsStartTransactionCommitted);
+ pw.println(prefix + "mIsSyncDrawRequested=" + mIsSyncDrawRequested);
+ pw.println(prefix + "mOriginalRotation=" + mOriginalRotation);
+ pw.println(prefix + "mTargetWindowTokens=" + mTargetWindowTokens);
+ }
+
/** The operation to control the rotation appearance associated with window token. */
private static class Operation {
@Retention(RetentionPolicy.SOURCE)
@@ -635,5 +654,10 @@
Operation(@Action int action) {
mAction = action;
}
+
+ @Override
+ public String toString() {
+ return "Operation{a=" + mAction + " pending=" + mIsCompletionPending + '}';
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 2ecbf8a..5aa7c97 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -87,7 +87,7 @@
private int mLastOrientation = ORIENTATION_UNDEFINED;
ContentRecorder(@NonNull DisplayContent displayContent) {
- this(displayContent, new RemoteMediaProjectionManagerWrapper());
+ this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
}
@VisibleForTesting
@@ -556,8 +556,14 @@
private static final class RemoteMediaProjectionManagerWrapper implements
MediaProjectionManagerWrapper {
+
+ private final int mDisplayId;
@Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
+ RemoteMediaProjectionManagerWrapper(int displayId) {
+ mDisplayId = displayId;
+ }
+
@Override
public void stopActiveProjection() {
fetchMediaProjectionManager();
@@ -565,12 +571,15 @@
return;
}
try {
+ ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: stopping active projection for display %d",
+ mDisplayId);
mIMediaProjectionManager.stopActiveProjection();
} catch (RemoteException e) {
ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
"Content Recording: Unable to tell MediaProjectionManagerService to stop "
- + "the active projection: %s",
- e);
+ + "the active projection for display %d: %s",
+ mDisplayId, e);
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index f24ba5a..b589085 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -117,10 +117,11 @@
}
incomingDisplayContent.setContentRecordingSession(incomingSession);
// Updating scenario: Explicitly ask ContentRecorder to update, since no config or
- // display change will trigger an update from the DisplayContent.
- if (hasSessionUpdatedWithConsent) {
- incomingDisplayContent.updateRecording();
- }
+ // display change will trigger an update from the DisplayContent. There exists a
+ // scenario where a DisplayContent is created, but it's ContentRecordingSession hasn't
+ // been set yet due to a race condition. On creation, updateRecording fails to start
+ // recording, so now this call guarantees recording will be started from somewhere.
+ incomingDisplayContent.updateRecording();
}
// Takeover and stopping scenario: stop recording on the pre-existing session.
if (mSession != null && !hasSessionUpdatedWithConsent) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 64c2c5d..ee3014c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3454,6 +3454,9 @@
if (mFixedRotationLaunchingApp != null) {
setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
}
+ } else if (mAsyncRotationController != null && !isRotationChanging()) {
+ Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+ finishAsyncRotationIfPossible();
}
return;
}
@@ -3627,6 +3630,9 @@
if (mFixedRotationLaunchingApp != null) {
pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
}
+ if (mAsyncRotationController != null) {
+ mAsyncRotationController.dump(pw, prefix);
+ }
pw.println();
pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 7d3c87a..ba242ec 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -63,6 +63,8 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -215,6 +217,11 @@
@Nullable
private final Boolean mBooleanPropertyAllowForceResizeOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowUserAspectRatioOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowUserAspectRatioFullscreenOverride;
+
/*
* WindowContainerListener responsible to make translucent activities inherit
* constraints from the first opaque activity beneath them. It's null for not
@@ -335,6 +342,15 @@
/* gatingCondition */ null,
PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ mBooleanPropertyAllowUserAspectRatioOverride =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled(),
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ mBooleanPropertyAllowUserAspectRatioFullscreenOverride =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled(),
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
mIsOverrideToPortraitOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -1109,7 +1125,8 @@
}
boolean shouldApplyUserMinAspectRatioOverride() {
- if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+ if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+ || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
|| mActivityRecord.mDisplayContent == null
|| !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
return false;
@@ -1122,7 +1139,9 @@
}
boolean shouldApplyUserFullscreenOverride() {
- if (!mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
+ if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+ || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
+ || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
|| mActivityRecord.mDisplayContent == null
|| !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
return false;
@@ -1151,7 +1170,8 @@
}
}
- private int getUserMinAspectRatioOverrideCode() {
+ @VisibleForTesting
+ int getUserMinAspectRatioOverrideCode() {
try {
return mActivityRecord.mAtmService.getPackageManager()
.getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c242554..81d939f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -600,7 +600,7 @@
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setDescription("descriptions for all");
- channel2.setSound(SOUND_URI, mAudioAttributes);
+ channel2.setSound(CANONICAL_SOUND_URI, mAudioAttributes);
channel2.enableLights(true);
channel2.setBypassDnd(true);
channel2.setLockscreenVisibility(VISIBILITY_SECRET);
@@ -1374,6 +1374,8 @@
.when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
doReturn(localUri)
.when(mTestIContentProvider).uncanonicalize(any(), eq(canonicalBasedOnLocal));
+ doReturn(canonicalBasedOnLocal)
+ .when(mTestIContentProvider).canonicalize(any(), eq(localUri));
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1387,7 +1389,7 @@
NotificationChannel actualChannel = mHelper.getNotificationChannel(
PKG_N_MR1, UID_N_MR1, channel.getId(), false);
- assertEquals(localUri, actualChannel.getSound());
+ assertEquals(canonicalBasedOnLocal, actualChannel.getSound());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 52226c2..4473a31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,6 +123,7 @@
controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
mWaitingDisplaySession);
+ verify(mVirtualDisplayContent).updateRecording();
// WHEN updating the session on the same display, so no longer waiting to record.
ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
@@ -135,7 +136,7 @@
// THEN the session was accepted.
assertThat(resultingSession).isEqualTo(sessionUpdate);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(sessionUpdate);
- verify(mVirtualDisplayContent).updateRecording();
+ verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index bdd178b..9cc4117 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2054,6 +2054,17 @@
assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
testPlayer.mLastReady.getChange(dcToken).getStartRotation());
testPlayer.finish();
+
+ // The AsyncRotationController should only exist if there is an ongoing rotation change.
+ dc.finishAsyncRotationIfPossible();
+ dc.setLastHasContent();
+ doReturn(dr.getRotation() + 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+ dr.updateRotationUnchecked(true /* forceUpdate */);
+ assertNotNull(dc.getAsyncRotationController());
+ doReturn(dr.getRotation() - 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+ dr.updateRotationUnchecked(true /* forceUpdate */);
+ assertNull("Cancel AsyncRotationController for the intermediate rotation changes 0->1->0",
+ dc.getAsyncRotationController());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 72ab18d..2ad9fa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -37,6 +37,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
@@ -48,6 +50,8 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -807,6 +811,108 @@
/* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
}
+ // shouldApplyUser...Override
+ @Test
+ public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+
+ assertTrue(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
+ throws Exception {
+ doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+ assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+ spyOn(mController);
+ doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+ }
+
+ private void prepareActivityThatShouldApplyUserFullscreenOverride() {
+ spyOn(mController);
+ doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+ .getUserMinAspectRatioOverrideCode();
+ }
+
// shouldUseDisplayLandscapeNaturalOrientation
@Test
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index ccc4ac2..58da4b43 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -368,29 +368,29 @@
/**
* This method is only used by VisualQueryDetector.
*/
- void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
}
final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
if (session == null) {
- return;
+ return false;
}
- session.startPerceivingLocked(callback);
+ return session.startPerceivingLocked(callback);
}
/**
* This method is only used by VisaulQueryDetector.
*/
- void stopPerceivingLocked() {
+ boolean stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
}
final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
if (session == null) {
- return;
+ return false;
}
- session.stopPerceivingLocked();
+ return session.stopPerceivingLocked();
}
public void startListeningFromExternalSourceLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 2e05e20..4720d27 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -95,7 +95,7 @@
}
@SuppressWarnings("GuardedBy")
- void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
}
@@ -198,15 +198,16 @@
mQueryStreaming = false;
}
};
- mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
+ return mRemoteDetectionService.run(
+ service -> service.detectWithVisualSignals(internalCallback));
}
@SuppressWarnings("GuardedBy")
- void stopPerceivingLocked() {
+ boolean stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
}
- mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
+ return mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 3574ef8..0de9255 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -23,7 +23,6 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -51,7 +50,6 @@
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
import android.media.permission.PermissionUtil;
import android.media.permission.SafeCloseable;
import android.os.Binder;
@@ -61,7 +59,6 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
-import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -88,6 +85,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -139,6 +137,7 @@
private final RemoteCallbackList<IVoiceInteractionSessionListener>
mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+ private IVisualQueryRecognitionStatusListener mVisualQueryRecognitionStatusListener;
public VoiceInteractionManagerService(Context context) {
super(context);
@@ -1346,6 +1345,17 @@
@android.annotation.EnforcePermission(
android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
@Override
+ public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+ listener) {
+ super.subscribeVisualQueryRecognitionStatus_enforcePermission();
+ synchronized (this) {
+ mVisualQueryRecognitionStatusListener = listener;
+ }
+ }
+
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+ @Override
public void enableVisualQueryDetection(
IVisualQueryDetectionAttentionListener listener) {
super.enableVisualQueryDetection_enforcePermission();
@@ -1391,7 +1401,10 @@
}
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.startPerceivingLocked(callback);
+ boolean success = mImpl.startPerceivingLocked(callback);
+ if (success && mVisualQueryRecognitionStatusListener != null) {
+ mVisualQueryRecognitionStatusListener.onStartPerceiving();
+ }
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -1409,7 +1422,10 @@
}
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.stopPerceivingLocked();
+ boolean success = mImpl.stopPerceivingLocked();
+ if (success && mVisualQueryRecognitionStatusListener != null) {
+ mVisualQueryRecognitionStatusListener.onStopPerceiving();
+ }
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d88a65..471acc1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -784,30 +784,30 @@
mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
}
- public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+ public boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
}
if (mHotwordDetectionConnection == null) {
// TODO: callback.onError();
- return;
+ return false;
}
- mHotwordDetectionConnection.startPerceivingLocked(callback);
+ return mHotwordDetectionConnection.startPerceivingLocked(callback);
}
- public void stopPerceivingLocked() {
+ public boolean stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
}
if (mHotwordDetectionConnection == null) {
Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
- return;
+ return false;
}
- mHotwordDetectionConnection.stopPerceivingLocked();
+ return mHotwordDetectionConnection.stopPerceivingLocked();
}
public void startListeningFromMicLocked(
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index f656881..c0d7cb4 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -63,6 +63,25 @@
}
@Test
+ public void testTransformImeLanguageTagToLocaleInfo_duplicateTagFilter() {
+ List<InputMethodSubtype> list = List.of(
+ new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(),
+ new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build());
+
+ Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list);
+
+ Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP");
+ assertEquals(localeSet.size(), expectedLanguageTag.size());
+ for (LocaleInfo info : localeSet) {
+ assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE);
+ assertTrue(expectedLanguageTag.contains(info.getId()));
+ }
+ }
+
+ @Test
public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();