diff options
Diffstat (limited to 'packages')
605 files changed, 15438 insertions, 7698 deletions
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index e2297e44fdfe..d2f514c6c0ca 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -29,6 +29,9 @@ <bool name="config_enableRightNavigationBar">false</bool> <bool name="config_enableBottomNavigationBar">true</bool> + <!-- Disable normal notification rendering; we handle that ourselves --> + <bool name="config_renderNotifications">false</bool> + <!-- Whether heads-up notifications should be shown when shade is open. --> <bool name="config_enableHeadsUpNotificationWhenNotificationShadeOpen">true</bool> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index cf4ee7d97409..585acfec410a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -22,7 +22,6 @@ import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; import android.content.Context; import com.android.systemui.car.CarDeviceProvisionedControllerImpl; -import com.android.systemui.car.CarNotificationEntryManager; import com.android.systemui.car.CarNotificationInterruptionStateProvider; import com.android.systemui.dagger.SystemUIRootComponent; import com.android.systemui.dock.DockManager; @@ -73,13 +72,6 @@ abstract class CarSystemUIModule { return false; } - /** - * Use {@link CarNotificationEntryManager}, which does nothing when adding a notification. - */ - @Binds - abstract NotificationEntryManager bindNotificationEntryManager( - CarNotificationEntryManager notificationEntryManager); - @Singleton @Provides static HeadsUpManagerPhone provideHeadsUpManagerPhone(Context context, diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java deleted file mode 100644 index cfe1c702663e..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ /dev/null @@ -1,62 +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.car; - -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.statusbar.FeatureFlags; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; -import com.android.systemui.statusbar.notification.logging.NotifLog; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.util.leak.LeakDetector; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.Lazy; - -/** - * Car specific notification entry manager that does nothing when adding a notification. - * - * <p> This is because system UI notifications are disabled and we have a different implementation. - * Please see {@link com.android.car.notification}. - */ -@Singleton -public class CarNotificationEntryManager extends NotificationEntryManager { - - @Inject - public CarNotificationEntryManager( - NotifLog notifLog, - NotificationGroupManager groupManager, - NotificationRankingManager rankingManager, - KeyguardEnvironment keyguardEnvironment, - FeatureFlags featureFlags, - Lazy<NotificationRowBinder> notificationRowBinderLazy, - Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, - LeakDetector leakDetector) { - super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags, - notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector); - } - - @Override - public void addNotification( - StatusBarNotification notification, NotificationListenerService.RankingMap ranking) { - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 76e9ec64e2f2..210dd321933a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -87,10 +87,8 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -103,12 +101,10 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -124,7 +120,6 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightsOutNotifController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; @@ -141,7 +136,6 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -257,7 +251,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt public CarStatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -269,13 +263,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -296,12 +288,10 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -318,7 +308,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt Optional<Recents> recents, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, SuperStatusBarViewFactory superStatusBarViewFactory, LightsOutNotifController lightsOutNotifController, @@ -334,7 +323,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry, /* Car Settings injected components. */ CarServiceProvider carServiceProvider, @@ -345,7 +333,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { super( context, - featureFlags, + notificationsController, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -357,13 +345,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationEntryManager, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, @@ -384,12 +370,10 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt vibratorHelper, bubbleController, groupManager, - groupAlertTransferHelper, visualStabilityManager, deviceProvisionedController, navigationBarController, assistManagerLazy, - notificationListener, configurationController, notificationShadeWindowController, lockscreenLockIconController, @@ -407,7 +391,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt recents, statusBarComponentBuilder, pluginManager, - remoteInputUriController, dividerOptional, lightsOutNotifController, statusBarNotificationActivityStarterBuilder, @@ -422,7 +405,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt keyguardDismissUtil, extensionController, userInfoControllerImpl, - notificationRowBinder, dismissCallbackRegistry); mUserSwitcherController = userSwitcherController; mScrimController = scrimController; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 45da8223943b..498bd8780f29 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -47,10 +47,8 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -63,12 +61,10 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; @@ -83,7 +79,6 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightsOutNotifController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; @@ -99,7 +94,6 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -127,7 +121,7 @@ public class CarStatusBarModule { @Singleton static CarStatusBar provideStatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -139,13 +133,11 @@ public class CarStatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> notifPipelineInitializer, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -166,12 +158,10 @@ public class CarStatusBarModule { VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -188,7 +178,6 @@ public class CarStatusBarModule { Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, SuperStatusBarViewFactory superStatusBarViewFactory, LightsOutNotifController lightsOutNotifController, @@ -204,7 +193,6 @@ public class CarStatusBarModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry, CarServiceProvider carServiceProvider, Lazy<PowerManagerHelper> powerManagerHelperLazy, @@ -214,7 +202,7 @@ public class CarStatusBarModule { FlingAnimationUtils.Builder flingAnimationUtilsBuilder) { return new CarStatusBar( context, - featureFlags, + notificationsController, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -226,13 +214,11 @@ public class CarStatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - notifPipelineInitializer, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationEntryManager, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, @@ -253,12 +239,10 @@ public class CarStatusBarModule { vibratorHelper, bubbleController, groupManager, - groupAlertTransferHelper, visualStabilityManager, deviceProvisionedController, navigationBarController, assistManagerLazy, - notificationListener, configurationController, notificationShadeWindowController, lockscreenLockIconController, @@ -275,7 +259,6 @@ public class CarStatusBarModule { recentsOptional, statusBarComponentBuilder, pluginManager, - remoteInputUriController, dividerOptional, superStatusBarViewFactory, lightsOutNotifController, @@ -290,7 +273,6 @@ public class CarStatusBarModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, - notificationRowBinder, dismissCallbackRegistry, carServiceProvider, powerManagerHelperLazy, diff --git a/packages/CarrierDefaultApp/res/values-ky/strings.xml b/packages/CarrierDefaultApp/res/values-ky/strings.xml index 066e8f6bcbaf..199476f47be0 100644 --- a/packages/CarrierDefaultApp/res/values-ky/strings.xml +++ b/packages/CarrierDefaultApp/res/values-ky/strings.xml @@ -12,6 +12,6 @@ <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Мобилдик Интернеттин абалы"</string> <string name="action_bar_label" msgid="4290345990334377177">"Мобилдик тармакка кирүү"</string> <string name="ssl_error_warning" msgid="3127935140338254180">"Кошулайын деген тармагыңызда коопсуздук көйгөйлөрү бар."</string> - <string name="ssl_error_example" msgid="6188711843183058764">"Мисалы, каттоо эсебине кирүү баракчасы көрсөтүлгөн уюмга таандык эмес болушу мүмкүн."</string> + <string name="ssl_error_example" msgid="6188711843183058764">"Мисалы, аккаунтка кирүү баракчасы көрсөтүлгөн уюмга таандык эмес болушу мүмкүн."</string> <string name="ssl_error_continue" msgid="1138548463994095584">"Баары бир серепчи аркылуу улантуу"</string> </resources> diff --git a/packages/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml index 9bd5be7b0dd7..7595d2b1eea3 100644 --- a/packages/DynamicSystemInstallationService/res/values/strings.xml +++ b/packages/DynamicSystemInstallationService/res/values/strings.xml @@ -35,4 +35,7 @@ <!-- Toast when we fail to launch into Dynamic System [CHAR LIMIT=64] --> <string name="toast_failed_to_reboot_to_dynsystem">Can\u2019t restart or load dynamic system</string> + <!-- URL of Dynamic System Key Revocation List [DO NOT TRANSLATE] --> + <string name="key_revocation_list_url" translatable="false">https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json</string> + </resources> diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 9ccb837cf613..9bae223a0a3e 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -46,6 +46,7 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.net.http.HttpResponseCache; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -60,6 +61,8 @@ import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -146,10 +149,26 @@ public class DynamicSystemInstallationService extends Service prepareNotification(); mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE); + + // Install an HttpResponseCache in the application cache directory so we can cache + // gsi key revocation list. The http(s) protocol handler uses this cache transparently. + // The cache size is chosen heuristically. Since we don't have too much traffic right now, + // a moderate size of 1MiB should be enough. + try { + File httpCacheDir = new File(getCacheDir(), "httpCache"); + long httpCacheSize = 1 * 1024 * 1024; // 1 MiB + HttpResponseCache.install(httpCacheDir, httpCacheSize); + } catch (IOException e) { + Log.d(TAG, "HttpResponseCache.install() failed: " + e); + } } @Override public void onDestroy() { + HttpResponseCache cache = HttpResponseCache.getInstalled(); + if (cache != null) { + cache.flush(); + } // Cancel the persistent notification. mNM.cancel(NOTIFICATION_ID); } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 9aea0e713179..438c435ef0e4 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -25,6 +25,8 @@ import android.os.image.DynamicSystemManager; import android.util.Log; import android.webkit.URLUtil; +import org.json.JSONException; + import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; @@ -100,7 +102,9 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog private final Context mContext; private final DynamicSystemManager mDynSystem; private final ProgressListener mListener; + private final boolean mIsNetworkUrl; private DynamicSystemManager.Session mInstallationSession; + private KeyRevocationList mKeyRevocationList; private boolean mIsZip; private boolean mIsCompleted; @@ -123,6 +127,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog mContext = context; mDynSystem = dynSystem; mListener = listener; + mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl); } @Override @@ -152,9 +157,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog return null; } + // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk) + mDynSystem.finishInstallation(); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, e.toString(), e); mDynSystem.remove(); return e; } finally { @@ -220,7 +227,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog String.format(Locale.US, "Unsupported file format: %s", mUrl)); } - if (URLUtil.isNetworkUrl(mUrl)) { + if (mIsNetworkUrl) { mStream = new URL(mUrl).openStream(); } else if (URLUtil.isFileUrl(mUrl)) { if (mIsZip) { @@ -234,6 +241,25 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog throw new UnsupportedUrlException( String.format(Locale.US, "Unsupported URL: %s", mUrl)); } + + // TODO(yochiang): Bypass this check if device is unlocked + try { + String listUrl = mContext.getString(R.string.key_revocation_list_url); + mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl)); + } catch (IOException | JSONException e) { + Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List"); + mKeyRevocationList = new KeyRevocationList(); + keyRevocationThrowOrWarning(e); + } + } + + private void keyRevocationThrowOrWarning(Exception e) throws Exception { + if (mIsNetworkUrl) { + throw e; + } else { + // If DSU is being installed from a local file URI, then be permissive + Log.w(TAG, e.toString()); + } } private void installUserdata() throws Exception { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java new file mode 100644 index 000000000000..522bc547325b --- /dev/null +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java @@ -0,0 +1,148 @@ +/* + * 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.dynsystem; + +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; + +class KeyRevocationList { + + private static final String TAG = "KeyRevocationList"; + + private static final String JSON_ENTRIES = "entries"; + private static final String JSON_PUBLIC_KEY = "public_key"; + private static final String JSON_STATUS = "status"; + private static final String JSON_REASON = "reason"; + + private static final String STATUS_REVOKED = "REVOKED"; + + @VisibleForTesting + HashMap<String, RevocationStatus> mEntries; + + static class RevocationStatus { + final String mStatus; + final String mReason; + + RevocationStatus(String status, String reason) { + mStatus = status; + mReason = reason; + } + } + + KeyRevocationList() { + mEntries = new HashMap<String, RevocationStatus>(); + } + + /** + * Returns the revocation status of a public key. + * + * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist. + */ + RevocationStatus getRevocationStatusForKey(String publicKey) { + return mEntries.get(publicKey); + } + + /** Test if a public key is revoked or not. */ + boolean isRevoked(String publicKey) { + RevocationStatus entry = getRevocationStatusForKey(publicKey); + return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED); + } + + @VisibleForTesting + void addEntry(String publicKey, String status, String reason) { + mEntries.put(publicKey, new RevocationStatus(status, reason)); + } + + /** + * Creates a KeyRevocationList from a JSON String. + * + * @param jsonString the revocation list, for example: + * <pre>{@code + * { + * "entries": [ + * { + * "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5", + * "status": "REVOKED", + * "reason": "Revocation Reason" + * } + * ] + * } + * }</pre> + * + * @throws JSONException if |jsonString| is malformed. + */ + static KeyRevocationList fromJsonString(String jsonString) throws JSONException { + JSONObject jsonObject = new JSONObject(jsonString); + KeyRevocationList list = new KeyRevocationList(); + Log.d(TAG, "Begin of revocation list"); + if (jsonObject.has(JSON_ENTRIES)) { + JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES); + for (int i = 0; i < entries.length(); ++i) { + JSONObject entry = entries.getJSONObject(i); + String publicKey = entry.getString(JSON_PUBLIC_KEY); + String status = entry.getString(JSON_STATUS); + String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : ""; + list.addEntry(publicKey, status, reason); + Log.d(TAG, "Revocation entry: " + entry.toString()); + } + } + Log.d(TAG, "End of revocation list"); + return list; + } + + /** + * Creates a KeyRevocationList from a URL. + * + * @throws IOException if |url| is inaccessible. + * @throws JSONException if fetched content is malformed. + */ + static KeyRevocationList fromUrl(URL url) throws IOException, JSONException { + Log.d(TAG, "Fetch from URL: " + url.toString()); + // Force "conditional GET" + // Force validate the cached result with server each time, and use the cached result + // only if it is validated by server, else fetch new data from server. + // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response + URLConnection connection = url.openConnection(); + connection.setUseCaches(true); + connection.addRequestProperty("Cache-Control", "max-age=0"); + try (InputStream stream = connection.getInputStream()) { + return fromJsonString(readFully(stream)); + } + } + + private static String readFully(InputStream in) throws IOException { + int n; + byte[] buffer = new byte[4096]; + StringBuilder builder = new StringBuilder(); + while ((n = in.read(buffer, 0, 4096)) > -1) { + builder.append(new String(buffer, 0, n)); + } + return builder.toString(); + } +} diff --git a/packages/DynamicSystemInstallationService/tests/Android.bp b/packages/DynamicSystemInstallationService/tests/Android.bp new file mode 100644 index 000000000000..3bdf82966889 --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/Android.bp @@ -0,0 +1,15 @@ +android_test { + name: "DynamicSystemInstallationServiceTests", + + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.runner", + "androidx.test.rules", + "mockito-target-minus-junit4", + ], + + resource_dirs: ["res"], + platform_apis: true, + instrumentation_for: "DynamicSystemInstallationService", + certificate: "platform", +} diff --git a/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml new file mode 100644 index 000000000000..f5f0ae6adaba --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dynsystem.tests"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.dynsystem" + android:label="Tests for DynamicSystemInstallationService" /> + +</manifest> diff --git a/packages/DynamicSystemInstallationService/tests/res/values/strings.xml b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml new file mode 100644 index 000000000000..fdb620bfe094 --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- testFromJsonString --> + <string name="blacklist_json_string" translatable="false"> + { + \"entries\":[ + { + \"public_key\":\"00fa2c6637c399afa893fe83d85f3569998707d5\", + \"status\":\"REVOKED\", + \"reason\":\"Key revocation test key\" + }, + { + \"public_key\":\"key2\", + \"status\":\"REVOKED\" + } + ] + } + </string> +</resources> diff --git a/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java new file mode 100644 index 000000000000..82ce542cf5de --- /dev/null +++ b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java @@ -0,0 +1,132 @@ +/* + * 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.dynsystem; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.Context; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * A test for KeyRevocationList.java + */ +@RunWith(AndroidJUnit4.class) +public class KeyRevocationListTest { + + private static final String TAG = "KeyRevocationListTest"; + + private static Context sContext; + + private static String sBlacklistJsonString; + + @BeforeClass + public static void setUpClass() throws Exception { + sContext = InstrumentationRegistry.getInstrumentation().getContext(); + sBlacklistJsonString = + sContext.getString(com.android.dynsystem.tests.R.string.blacklist_json_string); + } + + @Test + @SmallTest + public void testFromJsonString() throws JSONException { + KeyRevocationList blacklist; + blacklist = KeyRevocationList.fromJsonString(sBlacklistJsonString); + Assert.assertNotNull(blacklist); + Assert.assertFalse(blacklist.mEntries.isEmpty()); + blacklist = KeyRevocationList.fromJsonString("{}"); + Assert.assertNotNull(blacklist); + Assert.assertTrue(blacklist.mEntries.isEmpty()); + } + + @Test + @SmallTest + public void testFromUrl() throws IOException, JSONException { + URLConnection mockConnection = mock(URLConnection.class); + doReturn(new ByteArrayInputStream(sBlacklistJsonString.getBytes())) + .when(mockConnection).getInputStream(); + URL mockUrl = new URL( + "http", // protocol + "foo.bar", // host + 80, // port + "baz", // file + new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) { + return mockConnection; + } + }); + URL mockBadUrl = new URL( + "http", // protocol + "foo.bar", // host + 80, // port + "baz", // file + new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) throws IOException { + throw new IOException(); + } + }); + + KeyRevocationList blacklist = KeyRevocationList.fromUrl(mockUrl); + Assert.assertNotNull(blacklist); + Assert.assertFalse(blacklist.mEntries.isEmpty()); + + blacklist = null; + try { + blacklist = KeyRevocationList.fromUrl(mockBadUrl); + // Up should throw, down should be unreachable + Assert.fail("Expected IOException not thrown"); + } catch (IOException e) { + // This is expected, do nothing + } + Assert.assertNull(blacklist); + } + + @Test + @SmallTest + public void testIsRevoked() { + KeyRevocationList blacklist = new KeyRevocationList(); + blacklist.addEntry("key1", "REVOKED", "reason for key1"); + + KeyRevocationList.RevocationStatus revocationStatus = + blacklist.getRevocationStatusForKey("key1"); + Assert.assertNotNull(revocationStatus); + Assert.assertEquals(revocationStatus.mReason, "reason for key1"); + + revocationStatus = blacklist.getRevocationStatusForKey("key2"); + Assert.assertNull(revocationStatus); + + Assert.assertTrue(blacklist.isRevoked("key1")); + Assert.assertFalse(blacklist.isRevoked("key2")); + } +} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index ec445d4dcbee..617305cf6e5e 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -38,7 +38,6 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Path; import android.provider.DocumentsContract.Root; -import android.provider.MediaStore; import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; @@ -100,7 +99,6 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final String ROOT_ID_PRIMARY_EMULATED = DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID; - private static final String ROOT_ID_HOME = "home"; private static final String GET_DOCUMENT_URI_CALL = "get_document_uri"; private static final String GET_MEDIA_URI_CALL = "get_media_uri"; @@ -156,7 +154,6 @@ public class ExternalStorageProvider extends FileSystemProvider { private void updateVolumesLocked() { mRoots.clear(); - VolumeInfo primaryVolume = null; final int userId = UserHandle.myUserId(); final List<VolumeInfo> volumes = mStorageManager.getVolumes(); for (VolumeInfo volume : volumes) { @@ -234,8 +231,6 @@ public class ExternalStorageProvider extends FileSystemProvider { } if (volume.isPrimary()) { - // save off the primary volume for subsequent "Home" dir initialization. - primaryVolume = volume; root.flags |= Root.FLAG_ADVANCED; } // Dunno when this would NOT be the case, but never hurts to be correct. @@ -259,37 +254,6 @@ public class ExternalStorageProvider extends FileSystemProvider { } } - // Finally, if primary storage is available we add the "Documents" directory. - // If I recall correctly the actual directory is created on demand - // by calling either getPathForUser, or getInternalPathForUser. - if (primaryVolume != null && primaryVolume.isVisible()) { - final RootInfo root = new RootInfo(); - root.rootId = ROOT_ID_HOME; - mRoots.put(root.rootId, root); - root.title = getContext().getString(R.string.root_documents); - - // Only report bytes on *volumes*...as a matter of policy. - root.reportAvailableBytes = false; - root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH - | Root.FLAG_SUPPORTS_IS_CHILD; - - // Dunno when this would NOT be the case, but never hurts to be correct. - if (primaryVolume.isMountedWritable()) { - root.flags |= Root.FLAG_SUPPORTS_CREATE; - } - - // Create the "Documents" directory on disk (don't use the localized title). - root.visiblePath = new File( - primaryVolume.getPathForUser(userId), Environment.DIRECTORY_DOCUMENTS); - root.path = new File( - primaryVolume.getInternalPathForUser(userId), Environment.DIRECTORY_DOCUMENTS); - try { - root.docId = getDocIdForFile(root.path); - } catch (FileNotFoundException e) { - throw new IllegalStateException(e); - } - } - Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots"); // Note this affects content://com.android.externalstorage.documents/root/39BD-07C5 @@ -318,6 +282,16 @@ public class ExternalStorageProvider extends FileSystemProvider { return false; } + // Allow all directories on USB, including the root. + try { + RootInfo rootInfo = getRootFromDocId(docId); + if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { + return false; + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to determine rootInfo for docId"); + } + final String path = getPathFromDocId(docId); // Block the root of the storage diff --git a/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeApp.java b/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeApp.java index c9e0d0ab351d..d4eb2a9c75d0 100644 --- a/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeApp.java +++ b/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeApp.java @@ -16,6 +16,9 @@ package com.android.fakeoemfeatures; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + import android.app.ActivityManager; import android.app.ActivityThread; import android.app.AlertDialog; @@ -25,9 +28,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.util.Size; import android.util.Slog; import android.view.Display; import android.view.ViewGroup; @@ -94,8 +99,13 @@ public class FakeApp extends Application { return; } - final WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); + // Construct an instance of WindowManager to add the window of TYPE_APPLICATION_OVERLAY to + // the default display. + final DisplayManager dm = getSystemService(DisplayManager.class); + final Display defaultDisplay = dm.getDisplay(DEFAULT_DISPLAY); + final Context windowContext = createDisplayContext(defaultDisplay) + .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */); + final WindowManager wm = windowContext.getSystemService(WindowManager.class); // Check to make sure we are not running on a user build. If this // is a user build, WARN! Do not want! @@ -108,14 +118,14 @@ public class FakeApp extends Application { builder.setCancelable(false); builder.setPositiveButton("I understand", null); Dialog dialog = builder.create(); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + dialog.getWindow().setType(TYPE_APPLICATION_OVERLAY); dialog.show(); } // Make a fake window that is always around eating graphics resources. FakeView view = new FakeView(this); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); @@ -124,7 +134,8 @@ public class FakeApp extends Application { } lp.width = ViewGroup.LayoutParams.MATCH_PARENT; lp.height = ViewGroup.LayoutParams.MATCH_PARENT; - int maxSize = display.getMaximumSizeDimension(); + Size maxWindowSize = wm.getMaximumWindowMetrics().getSize(); + int maxSize = Math.max(maxWindowSize.getWidth(), maxWindowSize.getHeight()); maxSize *= 2; lp.x = maxSize; lp.y = maxSize; diff --git a/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeBackgroundService.java b/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeBackgroundService.java index ff09000b7bbd..df00eee63b50 100644 --- a/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeBackgroundService.java +++ b/packages/FakeOemFeatures/src/com/android/fakeoemfeatures/FakeBackgroundService.java @@ -16,6 +16,9 @@ package com.android.fakeoemfeatures; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + import java.util.ArrayList; import java.util.Random; @@ -23,9 +26,11 @@ import android.app.Dialog; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.util.Size; import android.view.Display; import android.view.ViewGroup; import android.view.WindowManager; @@ -68,13 +73,15 @@ public class FakeBackgroundService extends Service { super.onCreate(); mHandler.sendEmptyMessageDelayed(MSG_TICK, TICK_DELAY); - final WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); + final DisplayManager dm = getSystemService(DisplayManager.class); + final Display display = dm.getDisplay(DEFAULT_DISPLAY); + final Context windowContext = createDisplayContext(display) + .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */); // Make a fake window that is always around eating graphics resources. - FakeView view = new FakeView(this); - Dialog dialog = new Dialog(this, android.R.style.Theme_Holo_Dialog); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + FakeView view = new FakeView(windowContext); + Dialog dialog = new Dialog(windowContext, android.R.style.Theme_Holo_Dialog); + dialog.getWindow().setType(TYPE_APPLICATION_OVERLAY); dialog.getWindow().setFlags( WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE @@ -89,7 +96,11 @@ public class FakeBackgroundService extends Service { dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); WindowManager.LayoutParams lp = dialog.getWindow().getAttributes(); - int maxSize = display.getMaximumSizeDimension(); + // Create an instance of WindowManager that is adjusted to the area of the display dedicated + // for windows with type TYPE_APPLICATION_OVERLAY. + final WindowManager wm = windowContext.getSystemService(WindowManager.class); + Size maxWindowSize = wm.getMaximumWindowMetrics().getSize(); + int maxSize = Math.max(maxWindowSize.getWidth(), maxWindowSize.getHeight()); maxSize *= 2; lp.x = maxSize; lp.y = maxSize; diff --git a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml index a06dc546f85c..c4d8f35cec71 100644 --- a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml +++ b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml @@ -29,7 +29,7 @@ <service android:enabled="true" android:name="com.android.incremental.nativeadb.NativeAdbDataLoaderService" android:label="@string/app_name" - android:exported="true"> + android:exported="false"> <intent-filter> <action android:name="android.intent.action.LOAD_DATA" /> </intent-filter> diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java index bd5b79594c19..c4e41c87564f 100644 --- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java +++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java @@ -16,6 +16,8 @@ package com.android.incremental.nativeadb; +import android.annotation.NonNull; +import android.content.pm.DataLoaderParams; import android.service.dataloader.DataLoaderService; /** This code is used for testing only. */ @@ -26,7 +28,7 @@ public class NativeAdbDataLoaderService extends DataLoaderService { } @Override - public DataLoader onCreateDataLoader() { + public DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { return null; } } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 9c8345dafbc8..62124934f416 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -10,6 +10,7 @@ android_library { "androidx.appcompat_appcompat", "androidx.lifecycle_lifecycle-runtime", "androidx.mediarouter_mediarouter-nodeps", + "iconloader", "SettingsLibHelpUtils", "SettingsLibRestrictedLockUtils", diff --git a/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml new file mode 100644 index 000000000000..04de17474368 --- /dev/null +++ b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_blue.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z" + android:fillColor="#4285F4"/> +</vector> diff --git a/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml new file mode 100644 index 000000000000..b4145f23f61a --- /dev/null +++ b/packages/SettingsLib/LayoutPreference/res/drawable/ic_swap_horiz_grey.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z" + android:fillColor="#757575"/> +</vector> diff --git a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml new file mode 100644 index 000000000000..e6f8c01e22ac --- /dev/null +++ b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/entity_header" + style="@style/EntityHeader" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/entity_header_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/entity_header_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:id="@+id/entity_header_icon_personal" + android:layout_width="48dp" + android:layout_height="48dp" + android:scaleType="fitCenter" + android:antialias="true"/> + + <TextView + android:id="@+id/install_type" + style="@style/TextAppearance.EntityHeaderSummary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:text="Personal"/> + </LinearLayout> + + <ImageView + android:id="@+id/entity_header_swap_horiz" + android:layout_width="24dp" + android:layout_height="24dp" + android:scaleType="fitCenter" + android:antialias="true" + android:src="@drawable/ic_swap_horiz_grey"/> + + <LinearLayout + android:id="@+id/entity_header_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:id="@+id/entity_header_icon_work" + android:layout_width="48dp" + android:layout_height="48dp" + android:scaleType="fitCenter" + android:antialias="true"/> + <TextView + android:id="@+id/install_type" + style="@style/TextAppearance.EntityHeaderSummary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:text="Work"/> + </LinearLayout> + </LinearLayout> + +</RelativeLayout> diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 2b143e425589..1a015a697ee0 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Gekoppel via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beskikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te meld"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Gekoppel, geen internet nie"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet nie"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Daar kan nie by private DNS-bediener ingegaan word nie"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet nie"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rooi-groen)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blou-geel)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurregstelling"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kleurregstelling help mense met kleurblindheid om akkurater kleure te sien"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> tot battery gelaai is"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laai tans"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laai nie"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ingeprop; kan nie op die oomblik laai nie"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Vol"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 2a93e01765bb..05f0e1bd94d9 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"በ <xliff:g id="NAME">%1$s</xliff:g> በኩል ተገናኝተዋል"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"በ%1$s በኩል የሚገኝ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ለመመዝገብ መታ ያድርጉ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ተገናኝቷል፣ ምንም በይነመረብ የለም"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ምንም በይነመረብ የለም"</string> <string name="private_dns_broken" msgid="1984159464346556931">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"የተገደበ ግንኙነት"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ምንም በይነመረብ የለም"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ፕሮታኖማሊ (ቀይ-አረንጓዴ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ትራይታኖማሊ (ሰማያዊ-ቢጫ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"የቀለም ማስተካከያ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ቀለም ማስተካከያ የቀለም ማየት የማይችሉ ሰዎች ተጨማሪ ትክክለኛ ቀለማትን እንዲመለከቱ ያስችላቸዋል"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ኃይል እስከሚሞላ ድረስ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ኃይል በመሙላት ላይ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ባትሪ እየሞላ አይደለም"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ተሰክቷል፣ አሁን ኃይል መሙላት አይቻልም"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ሙሉነው"</string> diff --git a/packages/SettingsLib/res/values-ar/arrays.xml b/packages/SettingsLib/res/values-ar/arrays.xml index 851a3d8f136a..d65d821e71ab 100644 --- a/packages/SettingsLib/res/values-ar/arrays.xml +++ b/packages/SettingsLib/res/values-ar/arrays.xml @@ -252,7 +252,7 @@ <item msgid="3474333938380896988">"عرض مناطق العجز في رؤية اللونين الأخضر والأحمر"</item> </string-array> <string-array name="app_process_limit_entries"> - <item msgid="794656271086646068">"الحد القياسي"</item> + <item msgid="794656271086646068">"الحدّ العادي"</item> <item msgid="8628438298170567201">"ليست هناك عمليات بالخلفية"</item> <item msgid="915752993383950932">"عملية واحدة بحد أقصى"</item> <item msgid="8554877790859095133">"عمليتان بحد أقصى"</item> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 54856566e021..07befebde7ec 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"تم الاتصال عبر <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"متوفرة عبر %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"انقر للاشتراك."</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"متصلة ولكن بلا إنترنت"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"لا يتوفر اتصال إنترنت."</string> <string name="private_dns_broken" msgid="1984159464346556931">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"لا يتوفر اتصال إنترنت."</string> @@ -124,7 +124,7 @@ <string name="bluetooth_talkback_headset" msgid="3406852564400882682">"سماعة رأس"</string> <string name="bluetooth_talkback_phone" msgid="868393783858123880">"هاتف"</string> <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"تصوير"</string> - <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"سماعة أذن"</string> + <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"السمّاعة"</string> <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"جهاز إدخال طرفي"</string> <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"بلوتوث"</string> <string name="bluetooth_hearingaid_left_pairing_message" msgid="8561855779703533591">"جارٍ إقران سماعة الأذن الطبية اليسرى…"</string> @@ -157,7 +157,7 @@ <string name="tts_settings" msgid="8130616705989351312">"إعدادات تحويل النص إلى كلام"</string> <string name="tts_settings_title" msgid="7602210956640483039">"تحويل النص إلى كلام"</string> <string name="tts_default_rate_title" msgid="3964187817364304022">"معدل سرعة الكلام"</string> - <string name="tts_default_rate_summary" msgid="3781937042151716987">"سرعة نطق الكلام"</string> + <string name="tts_default_rate_summary" msgid="3781937042151716987">"سرعة قول الكلام"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"درجة الصوت"</string> <string name="tts_default_pitch_summary" msgid="9132719475281551884">"للتأثير في نبرة الكلام المُرَكَّب"</string> <string name="tts_default_lang_title" msgid="4698933575028098940">"اللغة"</string> @@ -181,7 +181,7 @@ <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"المحرّك المفضّل"</string> <string name="tts_general_section_title" msgid="8919671529502364567">"عامة"</string> <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"إعادة ضبط طبقة صوت الكلام"</string> - <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"إعادة ضبط طبقة الصوت التي يتم نطق النص بها على الإعداد التلقائي."</string> + <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"إعادة ضبط طبقة الصوت التي يتم قول النص بها على الإعداد التلقائي."</string> <string-array name="tts_rate_entries"> <item msgid="9004239613505400644">"بطيء جدًا"</item> <item msgid="1815382991399815061">"بطيء"</item> @@ -353,7 +353,7 @@ <string-array name="color_mode_names"> <item msgid="3836559907767149216">"نابض بالحياة (تلقائي)"</item> <item msgid="9112200311983078311">"طبيعي"</item> - <item msgid="6564241960833766170">"قياسي"</item> + <item msgid="6564241960833766170">"عادي"</item> </string-array> <string-array name="color_mode_descriptions"> <item msgid="6828141153199944847">"ألوان محسَّنة"</item> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"غطش الأحمر (الأحمر والأخضر)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"غمش الأزرق (الأزرق والأصفر)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحيح الألوان"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تساعد ميزة تصحيح الألوان المصابين بعمى الألوان على رؤية الألوان بدقة أكبر"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"جارٍ الشحن"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"لا يتم الشحن"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"تم التوصيل، ولكن يتعذّر الشحن الآن"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ممتلئة"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index fa26b9b93ef6..4d0d8cc4ba01 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>ৰ জৰিয়তে সংযুক্ত"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ছাইন আপ কৰিবলৈ টিপক"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"সংযোজিত, ইণ্টাৰনেট নাই"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইণ্টাৰনেট সংযোগ নাই"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ইণ্টাৰনেট সংযোগ সীমিত"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইণ্টাৰনেট সংযোগ নাই"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্ৰ’টানোমালি (ৰঙা-সেউজীয়া)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্ৰাইটান\'মেলী (নীলা-হালধীয়া)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ৰং শুধৰণী"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ৰং শুধৰণী কৰা কার্যই বর্ণান্ধলোকসকলক ৰংবোৰ অধিক সঠিককৈ দেখাত সহায় কৰে"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>ৰ দ্বাৰা অগ্ৰাহ্য কৰা হৈছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> চাৰ্জ হ\'বলৈ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চ্চাৰ্জ হৈ আছে"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চ্চাৰ্জ কৰা নাই"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ কৰি থোৱা হৈছে, এই মুহূৰ্তত চ্চাৰ্জ কৰিব নোৱাৰি"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"পূৰ্ণ"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 2e80fdcb8972..73eb48a02324 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ilə qoşulub"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s vasitəsilə əlçatandır"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Qeydiyyatdan keçmək üçün klikləyin"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Qoşuludur, internet yoxdur"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yoxdur"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Özəl DNS serverinə giriş mümkün deyil"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Məhdud bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yoxdur"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qırmızı-yaşıl)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (göy-sarı)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rəng düzəlişi"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Rəng düzəlişi rəng korluğu olanların daha yaxşı görməsinə kömək edir"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - Enerjinin dolmasına <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"enerji yığır"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Doldurulmur"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Cihaz hazırda batareya yığa bilmir"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Tam"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index ae5c93670644..dff657e34e69 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupna je preko pristupne tačke %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrovali"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Veza je uspostavljena, nema interneta"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Pristup privatnom DNS serveru nije uspeo"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boja"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boja pomaže ljudima koji su daltonisti da preciznije vide boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napuniće se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"puni se"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno je, ali punjenje trenutno nije moguće"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index ad201d081fce..12c21a6d7ba4 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Падключана праз праграму \"<xliff:g id="NAME">%1$s</xliff:g>\""</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Даступна праз %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Націсніце, каб зарэгістравацца"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Падключана, без доступу да інтэрнэту"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Не падключана да інтэрнэту"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Абмежаваныя магчымасці падключэння"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Не падключана да інтэрнэту"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Пратанамалія (чырвоны-зялёны)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Трытанамалія (сіні-жоўты)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Карэкцыя колеру"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Карэкцыя колеру дазваляе людзям з парушэннямі колеравага зроку лепш распазнаваць выявы на экране"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Зараду хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> да поўнай зарадкі"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ідзе зарадка"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не зараджаецца"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Падключана да сеткі сілкавання, зарадзіць зараз немагчыма"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятар зараджаны"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 94f78ad30a67..6fda5b358a7c 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Установена е връзка през <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Мрежата е достъпна през „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Докоснете, за да се регистрирате"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Установена е връзка – няма достъп до интернет"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Няма интернет"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се осъществи достъп до частния DNS сървър"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена връзка"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Няма връзка с интернет"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (червено – зелено)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синьо – жълто)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекция на цветове"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коригирането на цветовете помага на хората с цветна слепота да виждат по-точни цветове"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до пълно зареждане"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарежда се"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се зарежда"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Включена в захранването, в момента не се зарежда"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Пълна"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 517191601d2f..be2d1e0482be 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-এর মাধ্যমে কানেক্ট করা আছে"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s এর মাধ্যমে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"সাইন-আপ করতে ট্যাপ করুন"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"কানেক্ট, ইন্টারনেট নেই"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইন্টারনেট কানেকশন নেই"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"সীমিত কানেকশন"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইন্টারনেট কানেকশন নেই"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্রোটানোম্যালি (লাল-সবুজ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্রিট্যানোম্যালি (নীল-হলুদ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"রঙ সংশোধন"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"রঙ অ্যাডজাস্ট করার সেটিংস, বর্ণান্ধতা আছে এমন ব্যক্তিদের আরও সঠিকভাবে রঙ চিনতে সাহায্য করে"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ সম্পূর্ণ চার্জ হয়ে যাবে"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চার্জ হচ্ছে"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চার্জ হচ্ছে না"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ-ইন করা হয়েছে কিন্তু এখনই চার্জ করা যাবে না"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"পূর্ণ"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index f74bcee6fd08..c32df18d8f14 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupan preko %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite za prijavu"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Povezano, nema interneta"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema internetske veze"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS serveru"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema internetske veze"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ispravka boje"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ispravka boje pomaže daltonistima da preciznije vide boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključen, trenutno se ne može puniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 1b23ecfd904b..813fff4281d4 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connectat mitjançant <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible mitjançant %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca per registrar-te"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connectada, sense Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sense connexió a Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No es pot accedir al servidor DNS privat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexió limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sense connexió a Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermell-verd)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (blau-groc)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correcció del color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correcció del color ajuda les persones daltòniques a veure colors més precisos"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"s\'està carregant"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No s\'està carregant"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"El dispositiu està endollat però en aquests moments no es pot carregar"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 22603ccdd804..f116733375af 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Připojeno přes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupné prostřednictvím %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Klepnutím se zaregistrujete"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Připojeno, není k dispozici internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nejste připojeni k internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nelze získat přístup k soukromému serveru DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omezené připojení"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nejste připojeni k internetu"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomálie (červená a zelená)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomálie (modrá a žlutá)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekce barev"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekce barev pomáhá barvoslepým lidem vidět přesnější barvy"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do nabití"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíjení"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíjí se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Zapojeno, ale nelze nabíjet"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index bef1855570a1..e3b96a4bbd03 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Forbundet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgængelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryk for at registrere dig"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tilsluttet – intet internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Intet internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Der er ikke adgang til den private DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrænset forbindelse"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Intet internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopi (rød-grøn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopi (blå-gul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korriger farver"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Farvekorrigering gør det nemmere for farveblinde at se farver mere nøjagtigt"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"oplader"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Oplader ikke"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Enheden er tilsluttet en strømkilde. Det er ikke muligt at oplade på nuværende tidspunkt."</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fuldt"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index c8c97bdf3d42..1ddabcaba02f 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbunden über <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Verfügbar über %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Zum Anmelden tippen"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Verbunden, kein Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Kein Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Eingeschränkte Verbindung"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Kein Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (Rot-Grün-Sehschwäche)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (Blau-Gelb-Sehschwäche)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Farbkorrektur"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Die Farbkorrektur hilft farbenblinden Menschen, Farben besser zu erkennen"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> bis zur Aufladung"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"wird aufgeladen..."</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wird nicht geladen"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Angeschlossen, kann derzeit nicht geladen werden"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Voll"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 371075c7acc0..3a97d5a43291 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Συνδέθηκε μέσω <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Διαθέσιμο μέσω %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Πατήστε για εγγραφή"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Συνδέθηκε, χωρίς σύνδεση στο διαδίκτυο"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Χωρίς σύνδεση στο διαδίκτυο"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Περιορισμένη σύνδεση"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Δεν υπάρχει σύνδεση στο διαδίκτυο"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Πρωτανοπία (κόκκινο-πράσινο)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Τριτανοπία (μπλε-κίτρινο)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Διόρθωση χρωμάτων"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Η διόρθωση χρωμάτων βοηθάει τους ανθρώπους με αχρωματοψία να βλέπουν τα χρώματα με μεγαλύτερη ακρίβεια"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> για την ολοκλήρωση της φόρτισης"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"φόρτιση"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Δεν φορτίζει"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Συνδέθηκε, δεν είναι δυνατή η φόρτιση αυτήν τη στιγμή"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Πλήρης"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index b314d1782585..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index b314d1782585..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index b314d1782585..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index b314d1782585..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index 95944dc9eae1..3b286f7c0c9e 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connected, no internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Color correction helps people with color blindness to see more accurate colors"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge right now"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index c6dfdd3e3ac2..966f2f25e827 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conexión a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Presiona para registrarte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectado pero sin conexión a Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No se puede acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección de colores ayuda a las personas con daltonismo a ver colores más exactos"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar la carga"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando."</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. No se puede cargar en este momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Cargado"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index ba4a9ff9b245..11987221bdab 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para registrarte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conexión sin Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No se ha podido acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección del color ayuda a las personas con daltonismo a ver los colores más reales"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> hasta que termine de cargarse)"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Se ha conectado, pero no se puede cargar en este momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index df3b792b0e05..9be72f90ad06 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ühendatud võrgu <xliff:g id="NAME">%1$s</xliff:g> kaudu"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Saadaval üksuse %1$s kaudu"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Puudutage registreerumiseks"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ühendatud, Interneti-ühendus puudub"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Interneti pole"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Privaatsele DNS-serverile ei pääse juurde"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Piiratud ühendus"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Interneti-ühendus puudub"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaalia (punane-roheline)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaalia (sinine-kollane)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värvide korrigeerimine"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värviparanduse funktsioon aitab värvipimedatel värve täpsemini näha"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täislaadimiseni"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laadimine"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Vooluvõrgus, praegu ei saa laadida"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Täis"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index fcb320ffe61f..d0c6c52ca7c1 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> bidez konektatuta"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s bidez erabilgarri"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Sakatu erregistratzeko"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Konektatuta; ezin da atzitu Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ez dago Interneteko konexiorik"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ezin da atzitu DNS zerbitzari pribatua"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Konexio mugatua"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ez dago Interneteko konexiorik"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kolore-zuzenketak kolore zehatzagoak ikusten laguntzen die koloreentzako itsutasuna duten pertsonei."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"kargatzen"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ez da kargatzen ari"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Konektatuta dago. Ezin da kargatu une honetan."</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Beteta"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 261a438a4774..f15174a88395 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"متصل شده ازطریق <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"در دسترس از طریق %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"برای ثبتنام ضربه بزنید"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"متصل، بدون اینترنت"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"عدم اتصال به اینترنت"</string> <string name="private_dns_broken" msgid="1984159464346556931">"سرور DNS خصوصی قابل دسترسی نیست"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"عدم دسترسی به اینترنت"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"قرمزدشواربینی (قرمز-سبز)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"آبیدشواربینی (آبی-زرد)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحیح رنگ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تصحیح رنگ به افراد مبتلا به کوررنگی کمک میکند رنگها را دقیقتر ببینند"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"درحال شارژ شدن"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"شارژ نمیشود"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"به برق وصل شده است، درحالحاضر شارژ نمیشود"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"پر"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 4ccf430131af..1232db3ec9f7 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Yhdistetty (<xliff:g id="NAME">%1$s</xliff:g>)"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Käytettävissä seuraavan kautta: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Rekisteröidy napauttamalla"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Yhdistetty, ei internetyhteyttä"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ei internetyhteyttä"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ei pääsyä yksityiselle DNS-palvelimelle"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Rajallinen yhteys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ei internetyhteyttä"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (puna-vihersokeus)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (sini-keltasokeus)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värikorjaus"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värinkorjaus auttaa värisokeita tulkitsemaan värejä"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täyteen lataukseen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ladataan"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei laturissa"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kytketty virtalähteeseen, lataaminen ei onnistu"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Täynnä"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 84a97973d7c4..6404df4edb00 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté sur le réseau <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Accessible par %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toucher pour vous connecter"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connecté, aucun accès à Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucune connexion Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucune connexion Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu/jaune)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction des couleurs"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> : <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> : <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en cours de charge"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"N\'est pas en charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"L\'appareil est branché, mais il ne peut pas être chargé pour le moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 030a7f9b3b48..e75063b84985 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Appuyez ici pour vous connecter"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connecté, aucun accès à Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucun accès à Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucun accès à Internet"</string> @@ -57,7 +57,7 @@ <string name="osu_sign_up_complete" msgid="7640183358878916847">"Inscription terminée. Connexion…"</string> <string name="speed_label_very_slow" msgid="8526005255731597666">"Très lente"</string> <string name="speed_label_slow" msgid="6069917670665664161">"Lente"</string> - <string name="speed_label_okay" msgid="1253594383880810424">"Correct"</string> + <string name="speed_label_okay" msgid="1253594383880810424">"Correcte"</string> <string name="speed_label_medium" msgid="9078405312828606976">"Moyenne"</string> <string name="speed_label_fast" msgid="2677719134596044051">"Élevée"</string> <string name="speed_label_very_fast" msgid="8215718029533182439">"Très rapide"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu-jaune)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction couleur"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en charge…"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Pas en charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Appareil branché, mais impossible de le charger pour le moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index af033cf43328..7fd32f9c4e84 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Wifi conectada a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dispoñible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para rexistrarte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conexión sen Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Non hai conexión a Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Non se puido acceder ao servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Pouca conexión"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Non hai conexión a Internet"</string> @@ -316,7 +316,7 @@ <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Desactivar encamiñamento audio USB"</string> <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Desactiva o encamiñamento automático a periféricos de audio USB"</string> <string name="debug_layout" msgid="1659216803043339741">"Mostrar límites de deseño"</string> - <string name="debug_layout_summary" msgid="8825829038287321978">"Mostra os límites dos clips, as marxes, etc."</string> + <string name="debug_layout_summary" msgid="8825829038287321978">"Mostra os límites dos clips, as marxes etc."</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Forzar dirección do deseño RTL"</string> <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"Forza a dirección de pantalla a RTL (dereita a esquerda) para todas as configuración rexionais"</string> <string name="force_msaa" msgid="4081288296137775550">"Forzar MSAA 4x"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (vermello-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección da cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A corrección das cores axuda ás persoas con daltonismo a ver as cores con maior precisión"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> para completar a carga"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non se está cargando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectouse, pero non se pode cargar neste momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 87fd8767b326..1513c57694d6 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા કનેક્ટ થયેલ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s દ્વારા ઉપલબ્ધ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"સાઇન અપ કરવા માટે ટૅપ કરો"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"કનેક્ટ કર્યું, કોઈ ઇન્ટરનેટ નથી"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"કોઈ ઇન્ટરનેટ નથી"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"મર્યાદિત કનેક્શન"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ઇન્ટરનેટ ઍક્સેસ નથી"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"પ્રોટેનોમલી (લાલ-લીલો)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ટ્રાઇટેનોમલી(વાદળી-પીળો)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"રંગ સુધારણા"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"રંગ સુધારણા રંગ અંધત્વવાળા લોકોને વધુ સચોટ રંગો જોવામાં સહાય કરે છે"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જ થવા માટે <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ચાર્જ થઈ રહ્યું છે"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ચાર્જ થઈ રહ્યું નથી"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"પ્લગ ઇન કરેલ, હમણાં ચાર્જ કરી શકતા નથી"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"પૂર્ણ"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 02b5c9601ab4..c79c4a4090c6 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> के ज़रिए कनेक्ट किया गया"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s के द्वारा उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करने के लिए टैप करें"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"कनेक्ट हो गया है, लेकिन इंटरनेट नहीं है"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट कनेक्शन नहीं है"</string> <string name="private_dns_broken" msgid="1984159464346556931">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट कनेक्शन नहीं है"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"लाल रंग पहचान न पाना (लाल-हरा)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"नीला रंग पहचान न पाना (नीला-पीला)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधार"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग में सुधार करने की सेटिंग, वर्णान्धता (कलर ब्लाइंडनेस) वाले लोगों को ज़्यादा सटीक रंग देखने में मदद करती है"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में पूरा चार्ज हो जाएगा"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हो रही है"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज नहीं हो रही है"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन है, अभी चार्ज नहीं हो सकती"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूरी"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 598cfe2aadb2..3ab567866c76 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezan putem mreže <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupno putem %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrirali"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Povezano, bez interneta"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS poslužitelju"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno – zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo – žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boje"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boje pomaže slijepima za boje da vide preciznije boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Uključen, trenutačno se ne može puniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index d970c737c3d6..8d06f584631d 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Kapcsolódva a következőn keresztül: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Elérhető a következőn keresztül: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Koppintson a regisztrációhoz"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Csatlakozva, nincs internet-hozzáférés"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nincs internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Korlátozott kapcsolat"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nincs internetkapcsolat"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (piros– zöld)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (kék–sárga)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Színkorrekció"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A színkorrekció segít a színtévesztőknek abban, hogy pontosabban lássák a színeket"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a feltöltésig"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"töltés"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nem tölt"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Csatlakoztatva, jelenleg nem tölt"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Feltöltve"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 908693439de0..99cef2800ed6 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Միացված է <xliff:g id="NAME">%1$s</xliff:g>-ի միջոցով"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Հասանելի է %1$s-ի միջոցով"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Հպեք՝ գրանցվելու համար"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Միացված է, սակայն ինտերնետ կապ չկա"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Կապ չկա"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Մասնավոր DNS սերվերն անհասանելի է"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Սահմանափակ կապ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ինտերնետ կապ չկա"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Պրոտանոմալիա (կարմիր-կանաչ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Տրիտանոմալիա (կապույտ-դեղին)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Գունաշտկում"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Գունաշտկումն օգնում է գունային դալտոնիզմ ունեցող մարդկանց ավելի ճշգրիտ տեսնել գույները"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լիցքավորումը"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"լիցքավորում"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Չի լիցքավորվում"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Միացված է հոսանքի աղբյուրին, սակայն այս պահին չի կարող լիցքավորվել"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Լիցքավորված է"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 3c1504c7f14a..25de382b755a 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tersambung melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketuk untuk mendaftar"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tersambung, tidak ada internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tidak ada internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Server DNS pribadi tidak dapat diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Koneksi terbatas"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tidak ada internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koreksi warna"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Koreksi warna membantu orang penderita buta warna untuk melihat warna yang lebih akurat"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi terisi penuh"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengisi daya baterai"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengisi daya"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Tercolok, tidak dapat mengisi baterai sekarang"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 438e900be4ba..8fe382a698d4 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tenging í gegnum <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Í boði í gegnum %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ýttu til að skrá þig"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tengt, enginn netaðgangur"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Engin nettenging"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ekki næst í DNS-einkaþjón"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Takmörkuð tenging"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Engin nettenging"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Litblinda (rauðgræn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Litblinda (blágul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Litaleiðrétting"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Litaleiðrétting hjálpar fólki með litblindu að sjá réttari liti"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> að fullri hleðslu"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"í hleðslu"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ekki í hleðslu"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Í sambandi, ekki hægt að hlaða eins og er"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullhlaðin"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 114b33b49206..c9abc74fce07 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connesso tramite <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibile tramite %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tocca per registrarti"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Connesso, senza Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internet assente"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Non è possibile accedere al server DNS privato"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connessione limitata"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nessuna connessione a Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalìa (rosso-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalìa (blu-giallo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correzione del colore"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correzione del colore consente alle persone daltoniche di vedere colori più accurati"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla carica completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"in carica"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non in carica"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Collegato alla corrente. Impossibile caricare al momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carica"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 62085a85e243..1921d032775f 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"מחוברת באמצעות <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"זמינה דרך %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"יש להקיש כדי להירשם"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"מחובר. אין אינטרנט"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"אין אינטרנט"</string> <string name="private_dns_broken" msgid="1984159464346556931">"לא ניתן לגשת לשרת DNS הפרטי"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"חיבור מוגבל"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"אין אינטרנט"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"פרוטנומליה (אדום-ירוק)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"טריטנומליה (כחול-צהוב)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"תיקון צבע"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"תיקון צבע עוזר למשתמשים עם עיוורון צבעים לראות צבעים מדויקים יותר"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> עד הטעינה"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"בטעינה"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"לא בטעינה"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"המכשיר מחובר, אבל לא ניתן לטעון עכשיו"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"מלאה"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 28b98ee7c387..e4e1ab91b770 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> で接続しました"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s経由で使用可能"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"タップして登録してください"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"接続済み、インターネット接続なし"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"インターネットに接続されていません"</string> <string name="private_dns_broken" msgid="1984159464346556931">"プライベート DNS サーバーにアクセスできません"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"接続が制限されています"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"インターネット未接続"</string> @@ -203,7 +203,7 @@ <string name="vpn_settings_not_available" msgid="2894137119965668920">"このユーザーはVPN設定を利用できません。"</string> <string name="tethering_settings_not_available" msgid="266821736434699780">"このユーザーはテザリング設定を利用できません"</string> <string name="apn_settings_not_available" msgid="1147111671403342300">"このユーザーはアクセスポイント名設定を利用できません"</string> - <string name="enable_adb" msgid="8072776357237289039">"USBデバッグ"</string> + <string name="enable_adb" msgid="8072776357237289039">"USB デバッグ"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"USB接続時はデバッグモードにする"</string> <string name="clear_adb_keys" msgid="3010148733140369917">"USBデバッグの許可の取り消し"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"バグレポートのショートカット"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"第一色弱(赤緑)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"第三色弱(青黄)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色補正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色補正機能を利用すると、色覚異常のユーザーがより正確に色を判別できるようになります"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電完了まで <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電しています"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"充電していません"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"接続されていますが、現在、充電できません"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"フル"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 6f5d0b39fc1f..b2cfb17cdd43 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"დაკავშირებულია <xliff:g id="NAME">%1$s</xliff:g>-ით"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ხელმისაწვდომია %1$s-ით"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"შეეხეთ რეგისტრაციისთვის"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"დაკავშირებულია, ინტერნეტის გარეშე"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ინტერნეტ-კავშირი არ არის"</string> <string name="private_dns_broken" msgid="1984159464346556931">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"შეზღუდული კავშირი"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ინტერნეტ-კავშირი არ არის"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"პროტოანომალია (წითელი-მწვანე)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ტრიტანომალია (ლურჯი-ყვითელი)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ფერის კორექცია"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ფერის კორექცია ეხმარება ფერითი სიბრმავის მქონე ადამიანებს, უფრო ზუსტად გაარჩიონ ფერები"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> დატენვამდე"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"იტენება"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"არ იტენება"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"მიერთებულია, დატენვა ამჟამად ვერ ხერხდება"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ბატარეა დატენილია"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 3fe426e6d027..756bc06b630e 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> арқылы жалғанған"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s арқылы қолжетімді"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Тіркелу үшін түртіңіз."</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Қосылған, интернет жоқ"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернетпен байланыс жоқ."</string> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS серверіне кіру мүмкін емес."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Шектеулі байланыс"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернетпен байланыс жоқ"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (қызыл-жасыл)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсті түзету"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түсті түзету түсті ажырата алмайтын адамдарға оларды дәлірек көруге көмектеседі"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарядталуда"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Зарядталу орындалып жатқан жоқ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Қосылған, зарядталмайды"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Толы"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 24bfa35d2368..cdf92b38a46c 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"ភ្ជាប់តាម <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"មានតាមរយៈ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ចុចដើម្បីចុះឈ្មោះ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"បានភ្ជាប់ ប៉ុន្តែគ្មានអ៊ីនធឺណិតទេ"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"គ្មានអ៊ីនធឺណិតទេ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"មិនអាចចូលប្រើម៉ាស៊ីនមេ DNS ឯកជនបានទេ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ការតភ្ជាប់មានកម្រិត"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"គ្មានអ៊ីនធឺណិតទេ"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ក្រហមពណ៌បៃតង)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ពណ៌ខៀវ-លឿង)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ការកែពណ៌"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ការកែតម្រូវពណ៌ជួយដល់អ្នកដែលមិនអាចបែងចែកពណ៌ឱ្យមើលឃើញពណ៌ដែលត្រឹមត្រូវជាមុន"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"បដិសេធដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"នៅសល់ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបត្រូវសាក"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"មិនស្គាល់"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងបញ្ចូលថ្ម"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"កំពុងសាកថ្ម"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"មិនកំពុងបញ្ចូលថ្ម"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ដោតសាកថ្មរួចហើយ ប៉ុន្តែសាកថ្មមិនចូលទេឥឡូវនេះ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ពេញ"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 0699bbcd007a..4a24691f9606 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ಸೈನ್ ಅಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ, ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ಸೀಮಿತ ಸಂಪರ್ಕ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ಪ್ರೊಟನೋಮಲಿ (ಕೆಂಪು-ಹಸಿರು)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ಟ್ರಿಟನೋಮಲಿ (ನೀಲಿ-ಹಳದಿ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ವರ್ಣ ಅಂಧತ್ವ ಹೊಂದಿರುವ ಜನರಿಗೆ ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ವೀಕ್ಷಿಸಲು ಬಣ್ಣ ತಿದ್ದುಪಡಿ ಸಹಾಯ ಮಾಡುತ್ತದೆ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬೇಕು"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ಚಾರ್ಜ್ ಆಗುತ್ತಿಲ್ಲ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆ, ಇದೀಗ ಚಾರ್ಜ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ಭರ್ತಿ"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 25e9cfe8fa1a..51896e96f856 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>을(를) 통해 연결됨"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s을(를) 통해 사용 가능"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"탭하여 가입"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"연결됨, 인터넷 사용 불가"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"인터넷 연결 없음"</string> <string name="private_dns_broken" msgid="1984159464346556931">"비공개 DNS 서버에 액세스할 수 없습니다."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"제한된 연결"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"인터넷 연결 없음"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"적색약(적녹)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"청색약(청황)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"색보정"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"색상 보정을 사용하면 색맹인 사용자가 색상을 정확하게 구분하는 데 도움이 됩니다."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>, <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"남은 시간 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 완료까지 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"충전 중"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"충전 안함"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"전원이 연결되었지만 현재 충전할 수 없음"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"충전 완료"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 3dfce1eb2a8a..061f6fd37c6b 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> аркылуу туташты"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s аркылуу жеткиликтүү"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Катталуу үчүн таптап коюңуз"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Туташып турат, Интернет жок"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернет жок"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS сервери жеткиликсиз"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Байланыш чектелген"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернет жок"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (кызыл-жашыл)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсүн тууралоо"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түстү тууралоо жөндөөсү түстөрдү айырмалап көрбөгөн адамдарга түстөрдү тагыраак билүүгө жардам берет"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> кийин кубатталат"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"кубатталууда"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Кубат алган жок"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Сайылып турат, учурда кубаттоо мүмкүн эмес"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Толук"</string> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 48e50933c0ce..bfbd8a9a673e 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"ເຊື່ອມຕໍ່ຜ່ານ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ມີໃຫ້ຜ່ານ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ແຕະເພື່ອສະໝັກ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ເຊື່ອມຕໍ່ແລ້ວ, ບໍ່ມີອິນເຕີເນັດ"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ບໍ່ມີອິນເຕີເນັດ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ການເຊື່ອມຕໍ່ຈຳກັດ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ບໍ່ມີອິນເຕີເນັດ"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ສີແດງ-ສີຂຽວ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ສີຟ້າ-ສີເຫຼືອງ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ການປັບແຕ່ງສີ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ການແກ້ໄຂສີຈະຊ່ວຍໃຫ້ຄົນທີ່ຕາບອດສີເຫັນສີຕ່າງໆໄດ້ຖືກຕ້ອງຍິ່ງຂຶ້ນ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈົນກວ່າຈະສາກເຕັມ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ກຳລັງສາກໄຟ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ບໍ່ໄດ້ສາກໄຟ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ສຽບສາຍແລ້ວ, ບໍ່ສາມາດສາກໄດ້ໃນຕອນນີ້"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ເຕັມ"</string> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 8b3fbadbfb39..76d06faec154 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Prisijungta naudojant programą „<xliff:g id="NAME">%1$s</xliff:g>“"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pasiekiama naudojant „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Palieskite, kad prisiregistruotumėte"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Prisijungta, nėra interneto"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nėra interneto ryšio"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Privataus DNS serverio negalima pasiekti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ribotas ryšys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nėra interneto ryšio"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (raudona, žalia)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (mėlyna, geltona)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Spalvų taisymas"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Naudojant spalvų taisymo funkciją daltonikai gali geriau matyti spalvas"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nepaisyta naudojant nuostatą „<xliff:g id="TITLE">%1$s</xliff:g>“"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – iki visiškos įkrovos liko <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"įkraunama"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nekraunama"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Įjungta į maitinimo lizdą, bet šiuo metu įkrauti neįmanoma"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Visiškai įkrautas"</string> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index 4fc5b223c384..e4652d1a44eb 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Savienojums ar <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pieejams, izmantojot %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Pieskarieties, lai reģistrētos"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Savienojums izveidots, nav piekļuves internetam"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nav interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nevar piekļūt privātam DNS serverim."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ierobežots savienojums"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nav piekļuves internetam"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomālija (sarkans/zaļš)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomālija (zils/dzeltens)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Krāsu korekcija"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Krāsu korekcija palīdz cilvēkiem ar krāsu aklumu redzēt precīzākas krāsas."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> — <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"notiek uzlāde"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenotiek uzlāde"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pievienots, taču pašlaik nevar veikt uzlādi"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pilns"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 288e526be4c9..21b2f965d302 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Поврзано преку <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Достапно преку %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Допрете за да се регистрирате"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Поврзана, нема интернет"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернет"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се пристапи до приватниот DNS-сервер"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена врска"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернет"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (слепило за црвена и зелена)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (слепило за сина и жолта)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција на бои"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекцијата на бои им помага на луѓето со далтонизам попрецизно да ги гледаат боите"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до целосно полнење"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"се полни"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се полни"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Приклучен е, но батеријата не може да се полни во моментов"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Полна"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index e775297982fd..85aa000fc7f6 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> മുഖേന കണക്റ്റ് ചെയ്തു"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s വഴി ലഭ്യം"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"സൈൻ അപ്പ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"കണക്റ്റ് ചെയ്തു, ഇന്റർനെറ്റ് ഇല്ല"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ഇന്റർനെറ്റ് ഇല്ല"</string> <string name="private_dns_broken" msgid="1984159464346556931">"സ്വകാര്യ DNS സെർവർ ആക്സസ് ചെയ്യാനാവില്ല"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"പരിമിത കണക്ഷൻ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ഇന്റർനെറ്റ് ഇല്ല"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"പ്രോട്ടാനോമലി (ചുവപ്പ്-പച്ച)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ട്രിട്ടാനോമലി (നീല-മഞ്ഞ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"വർണ്ണം ക്രമീകരിക്കൽ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"വർണ്ണം ശരിയാക്കൽ, വർണ്ണാന്ധത ബാധിച്ച ആളുകൾക്ക് നിറങ്ങൾ കൂടുതൽ കൃത്യമായി കാണാൻ സഹായിക്കുന്നു"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമായി ചാർജാവാൻ <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ചാർജ് ചെയ്യുന്നു"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ചാർജ്ജുചെയ്യുന്നില്ല"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"പ്ലഗ് ഇൻ ചെയ്തു, ഇപ്പോൾ ചാർജ് ചെയ്യാനാവില്ല"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"നിറഞ്ഞു"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index ba69f9b654ab..232148a490ba 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-р холбогдсон"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s-р боломжтой"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Бүртгүүлэхийн тулд товшино уу"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Холбогдсон хэдий ч интернет алга"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернэт алга"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Хувийн DNS серверт хандах боломжгүй байна"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Хязгаарлагдмал холболт"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернэт алга"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Өнгөний залруулга нь өнгөний сохортой хүмүүст илүү оновчтой өнгө харахад тусалдаг"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - цэнэглэх хүртэл <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"цэнэглэж байна"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Цэнэглэхгүй байна"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Залгаастай тул одоо цэнэглэх боломжгүй"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Дүүрэн"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 1930cdf75e38..8767b4f145b4 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे कनेक्ट केले"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s द्वारे उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करण्यासाठी टॅप करा"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"कनेक्ट केले, इंटरनेट नाही"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट नाही"</string> <string name="private_dns_broken" msgid="1984159464346556931">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"मर्यादित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट नाही"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"क्षीण रक्तवर्णांधता (लाल-हिरवा)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"रंग दृष्टी कमतरता (निळा-पिवळा)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधारणा"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग सुधारणा ही वर्णांधता असलेल्या लोकांना रंग अधिक अचूक दिसण्यात मदत करते"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> पर्यंत पूर्ण चार्ज होईल"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज होत आहे"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज होत नाही"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन केलेले आहे, आता चार्ज करू शकत नाही"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index e4931883bacd..b79b80130d90 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Disambungkan melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketik untuk daftar"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Disambungkan, tiada Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tiada Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Pelayan DNS peribadi tidak boleh diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sambungan terhad"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tiada Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pembetulan warna"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Pembetulan warna membantu orang yang mengalami kebutaan warna melihat warna yang lebih tepat"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> sehingga dicas"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengecas"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengecas"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Dipalamkan, tidak boleh mengecas sekarang"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 2c4b32c04107..b2ad66418d53 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"အကောင့်ဖွင့်ရန် တို့ပါ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ချိတ်ဆက်ထားသည်၊ အင်တာနက်မရှိ"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"အင်တာနက် မရှိပါ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ချိတ်ဆက်မှု ကန့်သတ်ထားသည်"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"အင်တာနက် မရှိပါ"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (အနီ-အစိမ်း)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (အပြာ-အဝါ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"အရောင်ပြင်ဆင်မှု"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"အရောင်ပြင်ဆင်ခြင်းက အရောင်ကန်းသူများအတွက် ပိုမိုမှန်ကန်သော အရောင်များဖြင့် ကြည့်နိုင်ရန် ကူညီမည်"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားပြည့်ရန် <xliff:g id="TIME">%2$s</xliff:g> ကျန်သည်"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"အားသွင်းနေပါသည်"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"အားသွင်းမနေပါ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ပလပ်ထိုးထားသောကြောင့် ယခုအားသွင်း၍ မရသေးပါ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"အပြည့်"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 093c06f20132..6a1f158b50c7 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tilkoblet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgjengelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trykk for å registrere deg"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Tilkoblet – ingen Internett-tilgang"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ingen internettilkobling"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Den private DNS-tjeneren kan ikke nås"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrenset tilkobling"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ingen internettilkobling"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rød-grønn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blå-gul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Fargekorrigering"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med fargekorrigering kan personer med fargeblindhet se mer nøyaktige farger"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> gjenstår"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til batteriet er fulladet"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"lader"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Lader ikke"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Laderen er koblet til – kan ikke lade akkurat nå"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index fb8b7377e35d..9cd5e2079e9e 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> मार्फत जडान गरिएको"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s मार्फत उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप गर्न ट्याप गर्नुहोस्"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"जडान गरियो तर इन्टरनेट छैन"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इन्टरनेट छैन"</string> <string name="private_dns_broken" msgid="1984159464346556931">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित जडान"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इन्टरनेटमाथिको पहुँच छैन"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"प्रोटानेमली (रातो, हरियो)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ट्रिटानोमेली (निलो-पंहेलो)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रङ्ग सुधार"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रङ सुधार गर्नुले रङ छुट्याउन नसक्ने मान्छेलाई थप सही रङ देख्न मद्दत गर्दछ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"पूर्ण चार्ज हुन <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> लाग्छ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हुँदै"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज भइरहेको छैन"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लगइन गरिएको छ, अहिले नै चार्ज गर्न सकिँदैन"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण चार्ज भएको स्थिति"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 267dab4cdee8..8d5dea4e37d4 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbonden via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beschikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te melden"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Verbonden, geen internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Geen toegang tot privé-DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rood-groen)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blauw-geel)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurcorrectie"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Met behulp van kleurcorrectie kunnen mensen die kleurenblind zijn, nauwkeurigere kleuren te zien krijgen"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> tot opgeladen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"opladen"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wordt niet opgeladen"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Aangesloten, kan nu niet opladen"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Volledig"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index d8ae3bfb9a72..c0489c1dbc8f 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ଦ୍ବାରା ସଂଯୋଗ କରାଯାଇଛି"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ମାଧ୍ୟମରେ ଉପଲବ୍ଧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ସାଇନ୍ ଅପ୍ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ସଂଯୁକ୍ତ, ଇଣ୍ଟର୍ନେଟ୍ ନାହିଁ"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ସୀମିତ ସଂଯୋଗ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"କୌଣସି ଇଣ୍ଟରନେଟ୍ ନାହିଁ"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ପ୍ରୋଟାନୋମାଲି (ଲାଲ୍-ସବୁଜ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ନୀଳ-ହଳଦିଆ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ରଙ୍ଗ ସଠିକତା"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"କଲର୍ କରେକ୍ସନ୍ ରଙ୍ଗ ଚିହ୍ନିବାରେ ସମସ୍ୟା ଥିବା ଲୋକମାନଙ୍କୁ ଅଧିକ ସଠିକ୍ ରଙ୍ଗ ଦେଖିବାରେ ସାହାଯ୍ୟ କରିଥାଏ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ଦ୍ୱାରା ଓଭର୍ରାଇଡ୍ କରାଯାଇଛି"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ଚାର୍ଜ ହେବା ପର୍ଯ୍ୟନ୍ତ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ଚାର୍ଜ ହେଉଛି"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ଚାର୍ଜ ହେଉନାହିଁ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ପ୍ଲଗ୍ରେ ଲାଗିଛି, ହେଲେ ଏବେ ଚାର୍ଜ କରିପାରିବ ନାହିଁ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ଚାର୍ଜ ସମ୍ପୂର୍ଣ୍ଣ"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 6a784865b12c..7355418c4d13 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ਸਾਈਨ-ਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"ਕਨੈਕਟ ਕੀਤਾ, ਕੋਈ ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ਸੀਮਤ ਕਨੈਕਸ਼ਨ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ਲਾਲ-ਹਰਾ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ਨੀਲਾ-ਪੀਲਾ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ਰੰਗ ਸੁਧਾਈ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ਰੰਗ ਸੁਧਾਈ ਨਾਲ ਰੰਗਾਂ ਦੇ ਅੰਨ੍ਹਾਪਣ ਦੇ ਸ਼ਿਕਾਰ ਲੋਕਾਂ ਦੀ ਵਧੇਰੇ ਸਟੀਕ ਰੰਗਾਂ ਨੂੰ ਦੇਖਣ ਵਿੱਚ ਮਦਦ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ਤੱਕ ਚਾਰਜ ਹੋ ਜਾਵੇਗੀ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ਚਾਰਜ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ਪਲੱਗ ਲੱਗਾ ਹੋਇਆ ਹੈ, ਇਸ ਸਮੇਂ ਚਾਰਜ ਨਹੀਂ ਹੋ ਸਕਦੀ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ਪੂਰੀ"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 8c5547c2791d..a3ccb07a74a1 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Połączenie przez: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostępne przez %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kliknij, by się zarejestrować"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Połączono, brak internetu"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Brak internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Brak dostępu do prywatnego serwera DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograniczone połączenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brak internetu"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (czerwony-zielony)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (niebieski-żółty)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcja kolorów"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcja kolorów pomaga osobom z zaburzeniami rozpoznawania barw lepiej je widzieć."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – do naładowania <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ładowanie"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nie podłączony"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Podłączony. Nie można teraz ładować"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Naładowana"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 8c036164e140..f4182bfd7896 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectada, sem Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 6aeff1cb271c..2f206d472659 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ligado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível através de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ligado, sem Internet."</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível aceder ao servidor DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ligação limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção da cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cor ajuda as pessoas com daltonismo a ver cores mais precisas."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até ficar carregada"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"a carregar…"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ligada à corrente, não é possível carregar neste momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completo"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 8c036164e140..f4182bfd7896 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectada, sem Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 387441f32b56..6e8bbd14e649 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectat prin <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibilă prin %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Atingeți pentru a vă înscrie"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Conectată, fără internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Fără conexiune la internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Serverul DNS privat nu poate fi accesat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexiune limitată"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Fără conexiune la internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (roșu-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (albastru-galben)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corecția culorii"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Corecția culorii ajută persoanele cu daltonism să vadă culori mai exacte"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la încărcare"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"se încarcă"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nu se încarcă"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectat, nu se poate încărca chiar acum"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Complet"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 361e29ff452e..87831c76dc85 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Подключено через приложение \"<xliff:g id="NAME">%1$s</xliff:g>\"."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступно через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Нажмите, чтобы зарегистрироваться"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Подключено, без доступа к Интернету"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нет подключения к Интернету"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Доступа к частному DNS-серверу нет."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Подключение к сети ограничено."</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нет подключения к Интернету"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (красный/зеленый)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синий/желтый)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Коррекция цвета"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коррекция цвета помогает пользователям с нарушениями цветового зрения лучше различать изображение на экране."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряжается"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряжается"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Подключено, не заряжается"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Батарея заряжена"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index faa848fec830..f0a823a01ead 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> හරහා සම්බන්ධයි"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s හරහා ලබා ගැනීමට හැකිය"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ලියාපදිංචි වීමට තට්ටු කරන්න"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"සම්බන්ධයි, අන්තර්ජාලය නැත"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"අන්තර්ජාලය නැත"</string> <string name="private_dns_broken" msgid="1984159464346556931">"පුද්ගලික DNS සේවාදායකයට ප්රවේශ වීමට නොහැකිය"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"සීමිත සම්බන්ධතාව"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"අන්තර්ජාලය නැත"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"වර්ණ දුර්වලතාවය (රතු-කොළ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"වර්ණ අන්ධතාවය (නිල්-කහ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"වර්ණ නිවැරදි කිරීම"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"වර්ණ අන්ධතාවෙන් පෙළෙන පුද්ගලයන්ට වඩාත් නිරවද්ය වර්ණ බැලීමට වර්ණ නිවැරදි කිරීම සහාය වේ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වන තෙක් <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ආරෝපණය වේ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ආරෝපණය නොවේ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"පේනුගත කර ඇත, මේ අවස්ථාවේදී ආරෝපණය කළ නොහැකිය"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"පූර්ණ"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 2035d88c75f0..fc2ba1dee469 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Pripojené prostredníctvom siete <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"K dispozícii prostredníctvom %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Prihláste sa klepnutím"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Pripojené, žiadny internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Bez internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Obmedzené pripojenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Žiadny internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (červená a zelená)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (modrá a žltá)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Úprava farieb"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcia farieb pomáha farboslepým ľuďom vidieť presnejšie farby"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíja sa"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíja sa"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pripojené, ale nie je možné nabíjať"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 288961947269..cda2e067fdc2 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezava vzpostavljena prek omrežja <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Na voljo prek: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dotaknite se, če se želite registrirati"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Vzpostavljena povezava, brez interneta"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ni internetne povezave"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Do zasebnega strežnika DNS ni mogoče dostopati"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omejena povezava"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brez internetne povezave"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (rdeča – zelena)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (modra – rumena)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Popravljanje barv"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Popravljanje barv osebam z barvno slepoto pomaga, da vidijo bolj prave barve"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do polne napolnjenosti"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"polnjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Se ne polni"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno, trenutno ni mogoče polniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Poln"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index ccd4e3066d9f..9854017a6a25 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Lidhur përmes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"E mundshme përmes %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trokit për t\'u regjistruar"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"U lidh, por nuk ka internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nuk ka internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Serveri privat DNS nuk mund të qaset"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Lidhje e kufizuar"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nuk ka internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (e kuqe - e gjelbër)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (e kaltër - e verdhë)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korrigjimi i ngjyrës"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korrigjimi i ngjyrës i ndihmon njerëzit me daltonizëm të shohin ngjyra më të sakta"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> deri sa të karikohen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"po karikohet"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nuk po karikohet"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Në prizë, por nuk mund të ngarkohet për momentin"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"E mbushur"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 08e2bc85db4e..b59a568d1685 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Повезано преко: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступна је преко приступне тачке %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Додирните да бисте се регистровали"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Веза је успостављена, нема интернета"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернета"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Приступ приватном DNS серверу није успео"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена веза"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернета"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (црвено-зелено)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (плаво-жуто)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција боја"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекција боја помаже људима који су далтонисти да прецизније виде боје"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – напуниће се за <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"пуни се"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не пуни се"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Прикључено је, али пуњење тренутно није могуће"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Пуна"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index c0cdbc913aea..f9ed6d43a310 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Anslutet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tillgängligt via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryck för att logga in"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ansluten, inget internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Inget internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Det går inte att komma åt den privata DNS-servern."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begränsad anslutning"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Inget internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rött-grönt)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blått-gult)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Färgkorrigering"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med färgkorrigering kan färgblinda personer se mer korrekta färger"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> till full laddning"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laddas"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laddar inte"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Inkopplad, kan inte laddas just nu"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index f00dea3822cf..40e025a9b61f 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Imeunganishwa kupitia <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Inapatikana kupitia %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Gusa ili ujisajili"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Imeunganishwa, hakuna intaneti"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Hakuna intaneti"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Seva ya faragha ya DNS haiwezi kufikiwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Muunganisho hafifu"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Hakuna intaneti"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (nyekundu-kijani)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (samawati-manjano)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Usahihishaji wa rangi"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Urekebishaji wa rangi huwasaidia watu wenye matatizo ya kutofautisha rangi ili waone rangi nyingi sahihi"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hadi ijae chaji"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"inachaji"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Haichaji"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Haiwezi kuchaji kwa sasa"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Imejaa"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 52e0363147da..36a92ed1a706 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> மூலம் இணைக்கப்பட்டது"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s வழியாகக் கிடைக்கிறது"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"பதிவு செய்யத் தட்டவும்"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"இணைக்கப்பட்டுள்ளது, ஆனால் இண்டர்நெட் இல்லை"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"இணைய இணைப்பு இல்லை"</string> <string name="private_dns_broken" msgid="1984159464346556931">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"வரம்பிற்கு உட்பட்ட இணைப்பு"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"இணைய இணைப்பு இல்லை"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"வண்ணத்திருத்தம்"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"வண்ணத் திருத்தத்தால் நிறக்குருடு உள்ளவர்களால் வண்ணங்களை இன்னும் துல்லியமாகப் பார்க்க முடியும்"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழு சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"சார்ஜ் ஆகிறது"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"சார்ஜ் செய்யப்படவில்லை"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"செருகப்பட்டது, ஆனால் இப்போது சார்ஜ் செய்ய முடியவில்லை"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"முழுவதும் சார்ஜ் ஆனது"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index a39c4e14cddd..5ea380ccdf58 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా కనెక్ట్ చేయబడింది"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ద్వారా అందుబాటులో ఉంది"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"సైన్ అప్ చేయడానికి నొక్కండి"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"కనెక్ట్ చేయబడింది, ఇంటర్నెట్ లేదు"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ఇంటర్నెట్ లేదు"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ప్రైవేట్ DNS సర్వర్ను యాక్సెస్ చేయడం సాధ్యపడదు"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"పరిమిత కనెక్షన్"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ఇంటర్నెట్ లేదు"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ప్రొటానోమలీ (ఎరుపు-ఆకుపచ్చ రంగు)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ట్రైటనోమలీ (నీలం-పసుపు రంగు)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"రంగు సవరణ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"రంగు సవరణ అనేది వర్ణాంధత్వం ఉన్న వ్యక్తులకు మరింత ఖచ్చితమైన రంగులను చూడడానికి సహాయపడుతుంది"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%2$s</xliff:g> పడుతుంది"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ఛార్జ్ అవుతోంది"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ఛార్జ్ కావడం లేదు"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ప్లగ్ ఇన్ చేయబడింది, ప్రస్తుతం ఛార్జ్ చేయడం సాధ్యం కాదు"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"నిండింది"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 635d77a77a35..15e74ae1acf4 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"เชื่อมต่อแล้วผ่าน <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"พร้อมใช้งานผ่านทาง %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"แตะเพื่อลงชื่อสมัครใช้"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"เชื่อมต่อแล้ว ไม่พบอินเทอร์เน็ต"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ไม่มีอินเทอร์เน็ต"</string> <string name="private_dns_broken" msgid="1984159464346556931">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"การเชื่อมต่อที่จำกัด"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ไม่มีอินเทอร์เน็ต"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ตาบอดจางสีแดง (สีแดง/เขียว)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ตาบอดจางสีน้ำเงิน (สีน้ำเงิน/เหลือง)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"การแก้สี"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"การแก้ไขสีช่วยให้ผู้ที่มีอาการตาบอดสีเห็นสีต่างๆ ได้ตรงตามจริงยิ่งขึ้น"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> จนกว่าจะชาร์จ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"กำลังชาร์จ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ไม่ได้ชาร์จ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"เสียบอยู่ ไม่สามารถชาร์จได้ในขณะนี้"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"เต็ม"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 3f7f0ff56f8a..801224492c1e 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Nakakonekta sa pamamagitan ng <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available sa pamamagitan ng %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"I-tap para mag-sign up"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Nakakonekta, walang internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Walang internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Hindi ma-access ang pribadong DNS server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limitadong koneksyon"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Walang internet"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (pula-berde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (asul-dilaw)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pagtatama ng kulay"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ang pagwawasto ng kulay ay nakakatulong sa mga taong may color blindness na makita ang mga mas tamang kulay"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hanggang matapos mag-charge"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nagcha-charge"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hindi nagcha-charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Nakasaksak, hindi makapag-charge sa ngayon"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puno"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 7ad6fcd24ea1..b83ffcb16565 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ile bağlandı"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s üzerinden kullanılabilir"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kaydolmak için dokunun"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Bağlı, internet yok"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yok"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Gizli DNS sunucusuna erişilemiyor"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sınırlı bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yok"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Kırmızı renk körlüğü (kırmızı-yeşil)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mavi renk körlüğü (mavi-sarı)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Renk düzeltme"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Renk düzeltme, renk körlüğü olan kişilerin daha doğru renkler görmelerine yardımcı olur"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - şarj olmaya <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"şarj oluyor"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Şarj olmuyor"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Prize takıldı, şu anda şarj olamıyor"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Dolu"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index b5dd61898470..5fb46f8d8640 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Підключено через додаток <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступ через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Торкніться, щоб увійти"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Під’єднано, але немає доступу до Інтернету"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Немає Інтернету"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Немає доступу до приватного DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Обмежене з’єднання"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Немає Інтернету"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалія (червоний – зелений)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалія (синій – жовтий)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекція кольору"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекція кольору допомагає людям із дальтонізмом бачити точніші кольори"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряджається"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряджається"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Підключено. Не вдається зарядити"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятор заряджено"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index ef9b2a1b294e..c6c10f0fb8c9 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> کے ذریعے منسلک"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"دستیاب بذریعہ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"سائن اپ کے لیے تھپتھپائیں"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"منسلک، انٹرنیٹ نہیں ہے"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"انٹرنیٹ نہیں ہے"</string> <string name="private_dns_broken" msgid="1984159464346556931">"نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"محدود کنکشن"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"انٹرنیٹ نہیں ہے"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (سرخ سبز)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (نیلا پیلا)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"رنگ کی اصلاح"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"رنگ کی درستگی رنگ نہ دکھائی دینے والے لوگوں کی رنگوں کو مزید درست طریقے سے دیکھنے میں مدد کرتی ہے"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> چارج ہونے تک"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"چارج ہو رہا ہے"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"چارج نہیں ہو رہا ہے"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"پلگ ان ہے، ابھی چارج نہیں کر سکتے"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"مکمل"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index b202d6444965..07a6249b7e73 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> orqali ulandi"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s orqali ishlaydi"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Yozilish uchun bosing"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Ulangan, lekin internet aloqasi yo‘q"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internetga ulanmagansiz"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Xususiy DNS server ishlamayapti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Cheklangan aloqa"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Internet yo‘q"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qizil/yashil)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (ko‘k/sariq)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rangni tuzatish"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ranglarni sozlash ranglarni farqlashda muammosi bor insonlarga (masalan, daltoniklarga) aniq koʻrishda yordam beradi"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> ichida toʻliq quvvat oladi"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"quvvat olmoqda"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Quvvat olmayapti"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ulangan, lekin quvvat olmayapti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"To‘la"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index cda42d348c45..7e3944cb3349 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Đã kết nối qua <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Có sẵn qua %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Nhấn để đăng ký"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Đã kết nối, không có Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Không có Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Không thể truy cập máy chủ DNS riêng tư"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Kết nối giới hạn"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Không có Internet"</string> @@ -201,7 +201,7 @@ <string name="development_settings_summary" msgid="8718917813868735095">"Đặt tùy chọn cho phát triển ứng dụng"</string> <string name="development_settings_not_available" msgid="355070198089140951">"Tùy chọn dành cho nhà phát triển không khả dụng cho người dùng này"</string> <string name="vpn_settings_not_available" msgid="2894137119965668920">"Cài đặt VPN không khả dụng cho người dùng này"</string> - <string name="tethering_settings_not_available" msgid="266821736434699780">"Cài đặt chia sẻ kết nối không khả dụng cho người dùng này"</string> + <string name="tethering_settings_not_available" msgid="266821736434699780">"Cài đặt cách chia sẻ kết nối không khả dụng cho người dùng này"</string> <string name="apn_settings_not_available" msgid="1147111671403342300">"Cài đặt tên điểm truy cập không khả dụng cho người dùng này"</string> <string name="enable_adb" msgid="8072776357237289039">"Gỡ lỗi qua USB"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"Bật chế độ gỡ lỗi khi kết nối USB"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Mù màu đỏ không hoàn toàn (đỏ-xanh lục)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mù màu (xanh lam-vàng)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Sửa màu"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Tùy chọn sửa màu giúp những người bị mù màu thấy màu sắc chính xác hơn"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là sạc xong"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"đang sạc"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hiện không sạc"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Đã cắm nhưng không thể sạc ngay"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Đầy"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 418370b916e6..3edbb4d58b59 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"已通过<xliff:g id="NAME">%1$s</xliff:g>连接到网络"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可通过%1$s连接"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"点按即可注册"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已连接,但无法访问互联网"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"无法访问互联网"</string> <string name="private_dns_broken" msgid="1984159464346556931">"无法访问私人 DNS 服务器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"网络连接受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"无法访问互联网"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"红色弱视(红绿不分)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"蓝色弱视(蓝黄不分)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"颜色校正功能有助于色盲用户看到更准确的颜色"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>后充满电"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充电"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"未在充电"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入电源,但是现在无法充电"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"电量充足"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 79b55796bda2..6174fe91a4a3 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"已透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 連線"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕按即可登入"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已連線,但沒有互聯網"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有互聯網連線"</string> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有互聯網連線"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅綠)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍黃)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正有助色盲人士看到更準確的顏色"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還需 <xliff:g id="TIME">%2$s</xliff:g>才能充滿電"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充電"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入電源插座,但目前無法充電"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"電量已滿"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 47ab7641b7ad..a150d16f1b27 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 使用"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕觸即可註冊"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"已連線,沒有網際網路"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有網際網路連線"</string> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線能力受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有網際網路連線"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅-綠)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍-黃)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正可協助色盲使用者看見較準確的色彩"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽電"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電中"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已接上電源,但現在無法充電"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"電力充足"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 87f45de36c58..003dda399d18 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -42,7 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ixhumeke nge-<xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Iyatholakala nge-%1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Thepha ukuze ubhalisele"</string> - <string name="wifi_connected_no_internet" msgid="2381729074310543563">"Kuxhunyiwe, ayikho i-inthanethi"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ayikho i-inthanethi"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Iqoqo elikhawulelwe"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ayikho i-inthanethi"</string> @@ -383,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"I-Protanomaly (bomvu-luhlaza)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"I-Tritanomaly (luhlaza okwesibhakabhaka-phuzi)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ukulungiswa kombala"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ukulungisa umbala kusiza abantu abangaboni imibala ukubona ngokuqondile"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string> @@ -413,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ize igcwale"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"iyashaja"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ayishaji"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kuxhunyiwe, ayikwazi ukushaja khona manje"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Kugcwele"</string> diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml new file mode 100644 index 000000000000..332d6c7bc0fa --- /dev/null +++ b/packages/SettingsLib/res/values/config.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<!-- These resources are around just to allow their values to be customized --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V --> + <integer name="config_chargingSlowlyThreshold">5000000</integer> + + <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V --> + <integer name="config_chargingFastThreshold">7500000</integer> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index a7df6db3d31f..804e0cb021b7 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -129,7 +129,7 @@ <string name="certinstaller_package" translatable="false">com.android.certinstaller</string> <!-- Summary for Connected wifi network without internet --> - <string name="wifi_connected_no_internet">Connected, no internet</string> + <string name="wifi_connected_no_internet">No internet</string> <!-- Summary for connected network without internet due to private dns validation failed [CHAR LIMIT=NONE] --> <string name="private_dns_broken">Private DNS server cannot be accessed</string> @@ -553,6 +553,60 @@ <string name="enable_adb_summary">Debug mode when USB is connected</string> <!-- Setting title to revoke secure USB debugging authorizations --> <string name="clear_adb_keys">Revoke USB debugging authorizations</string> + <!-- [CHAR LIMIT=32] Setting title for ADB wireless switch --> + <string name="enable_adb_wireless">Wireless debugging</string> + <!-- [CHAR LIMIT=NONE] Setting checkbox summary for whether to enable Wireless debugging support on the phone --> + <string name="enable_adb_wireless_summary">Debug mode when Wi\u2011Fi is connected</string> + <!-- [CHAR LIMIT=32] Summary text when ADB wireless has error --> + <string name="adb_wireless_error">Error</string> + <!-- [CHAR LIMIT=32] Setting title for ADB wireless fragment --> + <string name="adb_wireless_settings">Wireless debugging</string> + <!-- [CHAR LIMIT=NONE] Wireless debugging settings. text displayed when wireless debugging is off and network list is empty. --> + <string name="adb_wireless_list_empty_off">To see and use available devices, turn on wireless debugging</string> + <!-- [CHAR LIMIT=50] Title for adb wireless pair by QR code preference --> + <string name="adb_pair_method_qrcode_title">Pair device with QR code</string> + <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by QR code preference --> + <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code Scanner</string> + <!-- [CHAR LIMIT=50] Title for adb wireless pair by pairing code preference --> + <string name="adb_pair_method_code_title">Pair device with pairing code</string> + <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by pairing code preference --> + <string name="adb_pair_method_code_summary">Pair new devices using six digit code</string> + <!-- [CHAR LIMIT=50] Title for adb wireless paired devices category --> + <string name="adb_paired_devices_title">Paired devices</string> + <!-- [CHAR LIMIT=50] Summary for adb wireless paired device preference --> + <string name="adb_wireless_device_connected_summary">Currently connected</string> + <!-- [CHAR LIMIT=50] Title for the adb device details fragment --> + <string name="adb_wireless_device_details_title">Device details</string> + <!-- [CHAR LIMIT=16] Button label to forget an adb device --> + <string name="adb_device_forget">Forget</string> + <!-- [CHAR LIMIT=50] Title format for mac address preference in adb device details fragment --> + <string name="adb_device_fingerprint_title_format">Device fingerprint: <xliff:g id="fingerprint_param" example="a1:b2:c3:d4:e5:f6">%1$s</xliff:g></string> + <!-- [CHAR LIMIT=50] Title for adb wireless connection failed dialog --> + <string name="adb_wireless_connection_failed_title">Connection unsuccessful</string> + <!-- [CHAR LIMIT=NONE] Message for adb wireless connection failed dialog --> + <string name="adb_wireless_connection_failed_message">Make sure <xliff:g id="device_name" example="Bob's Macbook">%1$s</xliff:g> is connected to the correct network</string> + <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog title --> + <string name="adb_pairing_device_dialog_title">Pair with device</string> + <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog pairing code label --> + <string name="adb_pairing_device_dialog_pairing_code_label">Wi\u2011Fi pairing code</string> + <!-- [CHAR LIMIT=50] Adb Wireless pairing device failed dialog title --> + <string name="adb_pairing_device_dialog_failed_title">Pairing unsuccessful</string> + <!-- [CHAR LIMIT=NONE] Adb wireless pairing device failed dialog message --> + <string name="adb_pairing_device_dialog_failed_msg">Make sure the device is connected to the same network.</string> + <!-- [CHAR LIMIT=NONE] Adb wireless qr code scanner description --> + <string name="adb_wireless_qrcode_summary">Pair device over Wi\u2011Fi by scanning a QR code</string> + <!-- [CHAR LIMIT=NONE] Adb wireless QR code pairing in progress text --> + <string name="adb_wireless_verifying_qrcode_text">Pairing device\u2026</string> + <!-- [CHAR LIMIT=NONE] Adb wireless QR code failed message --> + <string name="adb_qrcode_pairing_device_failed_msg">Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network.</string> + <!-- [CHAR LIMIT=50] Adb Wireless ip address and port title --> + <string name="adb_wireless_ip_addr_preference_title">IP address \u0026 Port</string> + <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing scanner title --> + <string name="adb_wireless_qrcode_pairing_title">Scan QR code</string> + <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing description --> + <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR Code</string> + <!--Adb wireless search Keywords [CHAR LIMIT=NONE]--> + <string name="keywords_adb_wireless">adb, debug, dev</string> <!-- [CHAR LIMIT=NONE] Setting checkbox title for Whether to include bug report item in power menu. --> <string name="bugreport_in_power">Bug report shortcut</string> <!-- [CHAR LIMIT=NONE] Setting checkbox summary for Whether to include bug report item in power --> @@ -689,6 +743,10 @@ <string name="adb_warning_title">Allow USB debugging?</string> <!-- Warning text to user about the implications of enabling USB debugging --> <string name="adb_warning_message">USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string> + <!-- Title of warning dialog about the implications of enabling USB debugging [CHAR LIMIT=NONE] --> + <string name="adbwifi_warning_title">Allow wireless debugging?</string> + <!-- Warning text to user about the implications of enabling USB debugging [CHAR LIMIT=NONE] --> + <string name="adbwifi_warning_message">Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string> <!-- Message of dialog confirming that user wants to revoke access to adb from all computers they have authorized --> <string name="adb_keys_warning_message">Revoke access to USB debugging from all computers you\u2019ve previously authorized?</string> <!-- Title of warning dialog about the implications of enabling developer settings --> @@ -970,7 +1028,7 @@ <!-- Title for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> <string name="accessibility_display_daltonizer_preference_title">Color correction</string> <!-- Subtitle for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] --> - <string name="accessibility_display_daltonizer_preference_subtitle">Color correction helps people with color blindness to see more accurate colors</string> + <string name="accessibility_display_daltonizer_preference_subtitle">Color correction helps people with colorblindness see more accurate colors</string> <!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] --> <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string> @@ -1034,8 +1092,10 @@ <string name="battery_info_status_unknown">Unknown</string> <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging from an unknown source. --> <string name="battery_info_status_charging">Charging</string> - <!-- [CHAR_LIMIT=20] Battery use screen with lower case. Battery status shown in chart label when charging from an unknown source. --> - <string name="battery_info_status_charging_lower">charging</string> + <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging speed is fast. --> + <string name="battery_info_status_charging_fast">Charging rapidly</string> + <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging speed is slow. --> + <string name="battery_info_status_charging_slow">Charging slowly</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_discharging">Not charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index de523d9f9bc8..f4857932064f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -3,6 +3,7 @@ package com.android.settingslib; import android.annotation.ColorInt; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -13,6 +14,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.media.AudioManager; @@ -27,9 +29,13 @@ import android.telephony.AccessNetworkConstants; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; +import com.android.launcher3.icons.IconFactory; import com.android.settingslib.drawable.UserIconDrawable; +import com.android.settingslib.fuelgauge.BatteryStatus; import java.text.NumberFormat; @@ -117,7 +123,7 @@ public class Utils { public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { final int iconSize = UserIconDrawable.getSizeForList(context); if (user.isManagedProfile()) { - Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); + Drawable drawable = UserIconDrawable.getManagedUserDrawable(context); drawable.setBounds(0, 0, iconSize, iconSize); return drawable; } @@ -159,20 +165,43 @@ public class Utils { return (level * 100) / scale; } - public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) { - int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, + /** + * Get battery status string + * + * @param context the context + * @param batteryChangedIntent battery broadcast intent received from {@link + * Intent.ACTION_BATTERY_CHANGED}. + * @return battery status string + */ + public static String getBatteryStatus(Context context, Intent batteryChangedIntent) { + final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); - String statusString; - if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - statusString = res.getString(R.string.battery_info_status_charging); - } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { - statusString = res.getString(R.string.battery_info_status_discharging); - } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { - statusString = res.getString(R.string.battery_info_status_not_charging); - } else if (status == BatteryManager.BATTERY_STATUS_FULL) { + final Resources res = context.getResources(); + + String statusString = res.getString(R.string.battery_info_status_unknown); + final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent); + + if (batteryStatus.isCharged()) { statusString = res.getString(R.string.battery_info_status_full); } else { - statusString = res.getString(R.string.battery_info_status_unknown); + if (status == BatteryManager.BATTERY_STATUS_CHARGING) { + switch (batteryStatus.getChargingSpeed(context)) { + case BatteryStatus.CHARGING_FAST: + statusString = res.getString(R.string.battery_info_status_charging_fast); + break; + case BatteryStatus.CHARGING_SLOWLY: + statusString = res.getString(R.string.battery_info_status_charging_slow); + break; + default: + statusString = res.getString(R.string.battery_info_status_charging); + break; + } + + } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { + statusString = res.getString(R.string.battery_info_status_discharging); + } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { + statusString = res.getString(R.string.battery_info_status_not_charging); + } } return statusString; @@ -206,7 +235,7 @@ public class Utils { /** * This method computes disabled color from normal color * - * @param context + * @param context the context * @param inputColor normal color. * @return disabled color. */ @@ -424,6 +453,19 @@ public class Utils { return state; } + /** + * Get the {@link Drawable} that represents the app icon + */ + public static @NonNull Drawable getBadgedIcon( + @NonNull Context context, @NonNull ApplicationInfo appInfo) { + final UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); + try (IconFactory iconFactory = IconFactory.obtain(context)) { + final Bitmap iconBmp = iconFactory.createBadgedIconBitmap( + appInfo.loadUnbadgedIcon(context.getPackageManager()), user, false).icon; + return new BitmapDrawable(context.getResources(), iconBmp); + } + } + private static boolean isNotInIwlan(ServiceState serviceState) { final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 19c666459723..af728887c917 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -59,6 +59,7 @@ import androidx.lifecycle.OnLifecycleEvent; import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import com.android.settingslib.Utils; import java.io.File; import java.io.IOException; @@ -495,7 +496,7 @@ public class ApplicationsState { return; } synchronized (entry) { - entry.ensureIconLocked(mContext, mDrawableFactory); + entry.ensureIconLocked(mContext); } } @@ -1216,7 +1217,7 @@ public class ApplicationsState { AppEntry entry = mAppEntries.get(i); if (entry.icon == null || !entry.mounted) { synchronized (entry) { - if (entry.ensureIconLocked(mContext, mDrawableFactory)) { + if (entry.ensureIconLocked(mContext)) { if (!mRunning) { mRunning = true; Message m = mMainHandler.obtainMessage( @@ -1587,10 +1588,10 @@ public class ApplicationsState { } } - boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) { + boolean ensureIconLocked(Context context) { if (this.icon == null) { if (this.apkFile.exists()) { - this.icon = drawableFactory.getBadgedIcon(info); + this.icon = Utils.getBadgedIcon(context, info); return true; } else { this.mounted = false; @@ -1601,7 +1602,7 @@ public class ApplicationsState { // its icon. if (this.apkFile.exists()) { this.mounted = true; - this.icon = drawableFactory.getBadgedIcon(info); + this.icon = Utils.getBadgedIcon(context, info); return true; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index a784e04ee6a0..1ebe91736ba1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -45,6 +46,7 @@ public class A2dpProfile implements LocalBluetoothProfile { private boolean mIsProfileReady; private final CachedBluetoothDeviceManager mDeviceManager; + private final BluetoothAdapter mBluetoothAdapter; static final ParcelUuid[] SINK_UUIDS = { BluetoothUuid.A2DP_SINK, @@ -99,7 +101,8 @@ public class A2dpProfile implements LocalBluetoothProfile { mContext = context; mDeviceManager = deviceManager; mProfileManager = profileManager; - BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new A2dpServiceListener(), + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBluetoothAdapter.getProfileProxy(context, new A2dpServiceListener(), BluetoothProfile.A2DP); } @@ -150,21 +153,6 @@ public class A2dpProfile implements LocalBluetoothProfile { return mService.getDevicesMatchingConnectionStates(states); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -173,8 +161,10 @@ public class A2dpProfile implements LocalBluetoothProfile { } public boolean setActiveDevice(BluetoothDevice device) { - if (mService == null) return false; - return mService.setActiveDevice(device); + if (mBluetoothAdapter == null) { + return false; + } + return mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_AUDIO); } public BluetoothDevice getActiveDevice() { @@ -182,31 +172,37 @@ public class A2dpProfile implements LocalBluetoothProfile { return mService.getActiveDevice(); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } boolean isA2dpPlaying() { if (mService == null) return false; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index 8ca5a74652dc..c7a5bd86c1cd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -115,21 +115,6 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { BluetoothProfile.STATE_DISCONNECTING}); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -137,31 +122,37 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } boolean isAudioPlaying() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 50d3a5daeb84..3aa35cb48c38 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -195,7 +195,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (newProfileState == BluetoothProfile.STATE_CONNECTED) { if (profile instanceof MapProfile) { - profile.setPreferred(mDevice, true); + profile.setEnabled(mDevice, true); } if (!mProfiles.contains(profile)) { mRemovedProfiles.remove(profile); @@ -208,7 +208,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } } else if (profile instanceof MapProfile && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { - profile.setPreferred(mDevice, false); + profile.setEnabled(mDevice, false); } else if (mLocalNapRoleConnected && profile instanceof PanProfile && ((PanProfile) profile).isLocalRoleNap(mDevice) && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { @@ -250,12 +250,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); if (PbapProfile != null && isConnectedProfile(PbapProfile)) { - PbapProfile.disconnect(mDevice); + PbapProfile.setEnabled(mDevice, false); } } public void disconnect(LocalBluetoothProfile profile) { - if (profile.disconnect(mDevice)) { + if (profile.setEnabled(mDevice, false)) { if (BluetoothUtils.D) { Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); } @@ -342,7 +342,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (!ensurePaired()) { return; } - if (profile.connect(mDevice)) { + if (profile.setEnabled(mDevice, true)) { if (BluetoothUtils.D) { Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index d65b5da22056..9dfc4d986745 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -45,6 +46,7 @@ public class HeadsetProfile implements LocalBluetoothProfile { private final CachedBluetoothDeviceManager mDeviceManager; private final LocalBluetoothProfileManager mProfileManager; + private final BluetoothAdapter mBluetoothAdapter; static final ParcelUuid[] UUIDS = { BluetoothUuid.HSP, @@ -99,7 +101,8 @@ public class HeadsetProfile implements LocalBluetoothProfile { LocalBluetoothProfileManager profileManager) { mDeviceManager = deviceManager; mProfileManager = profileManager; - BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new HeadsetServiceListener(), + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBluetoothAdapter.getProfileProxy(context, new HeadsetServiceListener(), BluetoothProfile.HEADSET); } @@ -111,21 +114,6 @@ public class HeadsetProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -134,10 +122,10 @@ public class HeadsetProfile implements LocalBluetoothProfile { } public boolean setActiveDevice(BluetoothDevice device) { - if (mService == null) { + if (mBluetoothAdapter == null) { return false; } - return mService.setActiveDevice(device); + return mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_PHONE_CALL); } public BluetoothDevice getActiveDevice() { @@ -161,31 +149,37 @@ public class HeadsetProfile implements LocalBluetoothProfile { return mService.getAudioState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index 9f1af669c708..a3b68b4b90b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -45,6 +46,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { static final String NAME = "HearingAid"; private final LocalBluetoothProfileManager mProfileManager; + private final BluetoothAdapter mBluetoothAdapter; // Order of this profile in device profiles list private static final int ORDINAL = 1; @@ -97,7 +99,8 @@ public class HearingAidProfile implements LocalBluetoothProfile { mContext = context; mDeviceManager = deviceManager; mProfileManager = profileManager; - BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBluetoothAdapter.getProfileProxy(context, new HearingAidServiceListener(), BluetoothProfile.HEARING_AID); } @@ -148,21 +151,6 @@ public class HearingAidProfile implements LocalBluetoothProfile { return mService.getDevicesMatchingConnectionStates(states); } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -171,8 +159,10 @@ public class HearingAidProfile implements LocalBluetoothProfile { } public boolean setActiveDevice(BluetoothDevice device) { - if (mService == null) return false; - return mService.setActiveDevice(device); + if (mBluetoothAdapter == null) { + return false; + } + return mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_ALL); } public List<BluetoothDevice> getActiveDevices() { @@ -180,31 +170,37 @@ public class HearingAidProfile implements LocalBluetoothProfile { return mService.getActiveDevices(); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public void setVolume(int volume) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index 678f2e37c6bf..66225a2bffca 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -125,23 +125,6 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - - @Override public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -150,7 +133,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public boolean isPreferred(BluetoothDevice device) { + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } @@ -158,7 +141,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public int getPreferred(BluetoothDevice device) { + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } @@ -166,17 +149,20 @@ final class HfpClientProfile implements LocalBluetoothProfile { } @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java index 35600b538d4d..8a2c4f8a1230 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -102,20 +104,6 @@ public class HidDeviceProfile implements LocalBluetoothProfile { } @Override - public boolean connect(BluetoothDevice device) { - // Don't invoke method in service because settings is not allowed to connect this profile. - return false; - } - - @Override - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.disconnect(device); - } - - @Override public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -124,21 +112,24 @@ public class HidDeviceProfile implements LocalBluetoothProfile { } @Override - public boolean isPreferred(BluetoothDevice device) { + public boolean isEnabled(BluetoothDevice device) { return getConnectionStatus(device) != BluetoothProfile.STATE_DISCONNECTED; } @Override - public int getPreferred(BluetoothDevice device) { + public int getConnectionPolicy(BluetoothDevice device) { return PREFERRED_VALUE; } @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; // if set preferred to false, then disconnect to the current device - if (!preferred) { - mService.disconnect(device); + if (!enabled) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 588083e73481..3c24b4a095b9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -101,20 +101,6 @@ public class HidProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -122,29 +108,37 @@ public class HidProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (mService == null) return; - if (preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java index 4b0ca7434f9a..f609e4311082 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java @@ -35,17 +35,26 @@ public interface LocalBluetoothProfile { */ boolean isAutoConnectable(); - boolean connect(BluetoothDevice device); - - boolean disconnect(BluetoothDevice device); - int getConnectionStatus(BluetoothDevice device); - boolean isPreferred(BluetoothDevice device); + /** + * Return {@code true} if the profile is enabled, otherwise return {@code false}. + * @param device the device to query for enable status + */ + boolean isEnabled(BluetoothDevice device); - int getPreferred(BluetoothDevice device); + /** + * Get the connection policy of the profile. + * @param device the device to query for enable status + */ + int getConnectionPolicy(BluetoothDevice device); - void setPreferred(BluetoothDevice device, boolean preferred); + /** + * Enable the profile if {@code enabled} is {@code true}, otherwise disable profile. + * @param device the device to set profile status + * @param enabled {@code true} for enable profile, otherwise disable profile. + */ + boolean setEnabled(BluetoothDevice device, boolean enabled); boolean isProfileReady(); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index ae2acbea8e4d..c72efb7eec83 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -528,14 +528,14 @@ public class LocalBluetoothProfileManager { (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { profiles.add(mMapProfile); removedProfiles.remove(mMapProfile); - mMapProfile.setPreferred(device, true); + mMapProfile.setEnabled(device, true); } if ((mPbapProfile != null) && (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { profiles.add(mPbapProfile); removedProfiles.remove(mPbapProfile); - mPbapProfile.setPreferred(device, true); + mPbapProfile.setEnabled(device, true); } if (mMapClientProfile != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 7d121aaa1ad1..19cb2f59f321 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -114,21 +114,6 @@ public final class MapClientProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -136,31 +121,37 @@ public final class MapClientProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index a96a4e73feea..75c1926683ef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.bluetooth.BluetoothAdapter; @@ -112,19 +113,6 @@ public class MapProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - Log.d(TAG, "connect() - should not get called"); - return false; // MAP never connects out - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -132,31 +120,37 @@ public class MapProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (enabled) { + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java index 8e3f3edcef10..5a6e6e8b830d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java @@ -40,27 +40,23 @@ final class OppProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - return false; - } - - public boolean disconnect(BluetoothDevice device) { - return false; - } - public int getConnectionStatus(BluetoothDevice device) { return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return false; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle OPP } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + return false; } public boolean isProfileReady() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java index 6638592e8be5..767df352b70f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -83,22 +86,6 @@ public class PanProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - List<BluetoothDevice> sinks = mService.getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -106,16 +93,36 @@ public class PanProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return true; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return -1; } - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PAN + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + + if (enabled) { + final List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN); + } + } + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + } else { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 56267fc596cf..0d11293a01b7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -126,23 +126,6 @@ public final class PbapClientProfile implements LocalBluetoothProfile { BluetoothProfile.STATE_DISCONNECTING}); } - public boolean connect(BluetoothDevice device) { - Log.d(TAG,"PBAPClientProfile got connect request"); - if (mService == null) { - return false; - } - Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress()); - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - Log.d(TAG,"PBAPClientProfile got disconnect request"); - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -150,31 +133,37 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index f7c0bf5c8c9d..9e2e4a14124a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -91,34 +91,33 @@ public class PbapServerProfile implements LocalBluetoothProfile { return false; } - public boolean connect(BluetoothDevice device) { - /*Can't connect from server */ - return false; - - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { return false; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { return -1; } - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PBAP + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + + if (!enabled) { + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 3022c5b566eb..104f1d738000 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -111,21 +111,6 @@ final class SapProfile implements LocalBluetoothProfile { return true; } - public boolean connect(BluetoothDevice device) { - if (mService == null) { - return false; - } - return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); - } - - public boolean disconnect(BluetoothDevice device) { - if (mService == null) { - return false; - } - - return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); - } - public int getConnectionStatus(BluetoothDevice device) { if (mService == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -133,31 +118,37 @@ final class SapProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } - public boolean isPreferred(BluetoothDevice device) { + @Override + public boolean isEnabled(BluetoothDevice device) { if (mService == null) { return false; } return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } - public int getPreferred(BluetoothDevice device) { + @Override + public int getConnectionPolicy(BluetoothDevice device) { if (mService == null) { return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } - public void setPreferred(BluetoothDevice device, boolean preferred) { + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; if (mService == null) { - return; + return false; } - if (preferred) { + if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } + + return isEnabled; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java index aac7fc3c927a..d48aa246ecba 100644 --- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java +++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java @@ -402,7 +402,7 @@ public class ZoneGetter { private static List<String> extractTimeZoneIds(List<TimeZoneMapping> timeZoneMappings) { final List<String> zoneIds = new ArrayList<>(timeZoneMappings.size()); for (TimeZoneMapping timeZoneMapping : timeZoneMappings) { - zoneIds.add(timeZoneMapping.timeZoneId); + zoneIds.add(timeZoneMapping.getTimeZoneId()); } return Collections.unmodifiableList(zoneIds); } diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java new file mode 100644 index 000000000000..bc40903d88e4 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -0,0 +1,147 @@ +/* + * 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.settingslib.fuelgauge; + +import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; +import static android.os.BatteryManager.BATTERY_STATUS_FULL; +import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import static android.os.BatteryManager.EXTRA_HEALTH; +import static android.os.BatteryManager.EXTRA_LEVEL; +import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT; +import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; +import static android.os.BatteryManager.EXTRA_PLUGGED; +import static android.os.BatteryManager.EXTRA_STATUS; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; + +import com.android.settingslib.R; + +/** + * Stores and computes some battery information. + */ +public class BatteryStatus { + private static final int LOW_BATTERY_THRESHOLD = 20; + private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000; + + public static final int CHARGING_UNKNOWN = -1; + public static final int CHARGING_SLOWLY = 0; + public static final int CHARGING_REGULAR = 1; + public static final int CHARGING_FAST = 2; + + public final int status; + public final int level; + public final int plugged; + public final int health; + public final int maxChargingWattage; + + public BatteryStatus(int status, int level, int plugged, int health, + int maxChargingWattage) { + this.status = status; + this.level = level; + this.plugged = plugged; + this.health = health; + this.maxChargingWattage = maxChargingWattage; + } + + public BatteryStatus(Intent batteryChangedIntent) { + status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); + plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0); + level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0); + health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); + + 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; + } + } + + /** + * Determine whether the device is plugged in (USB, power, or wireless). + * + * @return true if the device is plugged in. + */ + public boolean isPluggedIn() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB + || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; + } + + /** + * Determine whether the device is plugged in (USB, power). + * + * @return true if the device is plugged in wired (as opposed to wireless) + */ + public boolean isPluggedInWired() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } + + /** + * Whether or not the device is charged. Note that some devices never return 100% for + * battery level, so this allows either battery level or status to determine if the + * battery is charged. + * + * @return true if the device is charged + */ + public boolean isCharged() { + return status == BATTERY_STATUS_FULL || level >= 100; + } + + /** + * Whether battery is low and needs to be charged. + * + * @return true if battery is low + */ + public boolean isBatteryLow() { + return level < LOW_BATTERY_THRESHOLD; + } + + /** + * Return current chargin speed is fast, slow or normal. + * + * @return the charing speed + */ + public final int getChargingSpeed(Context context) { + final int slowThreshold = context.getResources().getInteger( + R.integer.config_chargingSlowlyThreshold); + final int fastThreshold = context.getResources().getInteger( + R.integer.config_chargingFastThreshold); + return maxChargingWattage <= 0 ? CHARGING_UNKNOWN : + maxChargingWattage < slowThreshold ? CHARGING_SLOWLY : + maxChargingWattage > fastThreshold ? CHARGING_FAST : + CHARGING_REGULAR; + } + + @Override + public String toString() { + return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged + + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}"; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java index 9db4a35c1d78..b4c95e6ee2df 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java @@ -35,8 +35,10 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.zip.GZIPInputStream; /** @@ -84,7 +86,7 @@ class LicenseHtmlGeneratorFromXml { * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2. */ - private final Map<String, String> mFileNameToContentIdMap = new HashMap(); + private final Map<String, Set<String>> mFileNameToContentIdMap = new HashMap(); /* * A map from a content id (MD5 sum of file content) to a license file content. @@ -186,10 +188,10 @@ class LicenseHtmlGeneratorFromXml { * </licenses> */ @VisibleForTesting - static void parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap, + static void parse(InputStreamReader in, Map<String, Set<String>> outFileNameToContentIdMap, Map<String, String> outContentIdToFileContentMap) throws XmlPullParserException, IOException { - Map<String, String> fileNameToContentIdMap = new HashMap<String, String>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<String, Set<String>>(); Map<String, String> contentIdToFileContentMap = new HashMap<String, String>(); XmlPullParser parser = Xml.newPullParser(); @@ -206,7 +208,10 @@ class LicenseHtmlGeneratorFromXml { if (!TextUtils.isEmpty(contentId)) { String fileName = readText(parser).trim(); if (!TextUtils.isEmpty(fileName)) { - fileNameToContentIdMap.put(fileName, contentId); + Set<String> contentIds = + fileNameToContentIdMap.computeIfAbsent( + fileName, k -> new HashSet<>()); + contentIds.add(contentId); } } } else if (TAG_FILE_CONTENT.equals(parser.getName())) { @@ -224,7 +229,13 @@ class LicenseHtmlGeneratorFromXml { state = parser.next(); } - outFileNameToContentIdMap.putAll(fileNameToContentIdMap); + for (Map.Entry<String, Set<String>> entry : fileNameToContentIdMap.entrySet()) { + outFileNameToContentIdMap.merge( + entry.getKey(), entry.getValue(), (s1, s2) -> { + s1.addAll(s2); + return s1; + }); + } outContentIdToFileContentMap.putAll(contentIdToFileContentMap); } @@ -240,7 +251,7 @@ class LicenseHtmlGeneratorFromXml { } @VisibleForTesting - static void generateHtml(Map<String, String> fileNameToContentIdMap, + static void generateHtml(Map<String, Set<String>> fileNameToContentIdMap, Map<String, String> contentIdToFileContentMap, PrintWriter writer, String noticeHeader) { List<String> fileNameList = new ArrayList(); @@ -259,19 +270,20 @@ class LicenseHtmlGeneratorFromXml { // Prints all the file list with a link to its license file content. for (String fileName : fileNameList) { - String contentId = fileNameToContentIdMap.get(fileName); - // Assigns an id to a newly referred license file content. - if (!contentIdToOrderMap.containsKey(contentId)) { - contentIdToOrderMap.put(contentId, count); - - // An index in contentIdAndFileNamesList is the order of each element. - contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); - count++; - } + for (String contentId : fileNameToContentIdMap.get(fileName)) { + // Assigns an id to a newly referred license file content. + if (!contentIdToOrderMap.containsKey(contentId)) { + contentIdToOrderMap.put(contentId, count); + + // An index in contentIdAndFileNamesList is the order of each element. + contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); + count++; + } - int id = contentIdToOrderMap.get(contentId); - contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); - writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); + int id = contentIdToOrderMap.get(contentId); + contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); + writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); + } } writer.println(HTML_MIDDLE_STRING); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 3a53d29f7618..e551b69e024a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -19,7 +19,8 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; -import android.util.Log; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import android.util.Pair; import com.android.settingslib.R; @@ -35,8 +36,9 @@ public class BluetoothMediaDevice extends MediaDevice { private CachedBluetoothDevice mCachedDevice; - BluetoothMediaDevice(Context context, CachedBluetoothDevice device) { - super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE); + BluetoothMediaDevice(Context context, CachedBluetoothDevice device, + MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { + super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE, routerManager, info, packageName); mCachedDevice = device; initDeviceRecord(); } @@ -65,20 +67,6 @@ public class BluetoothMediaDevice extends MediaDevice { return MediaDeviceUtils.getId(mCachedDevice); } - @Override - public boolean connect() { - //TODO(b/117129183): add callback to notify LocalMediaManager connection state. - final boolean isConnected = mCachedDevice.setActive(); - setConnectedRecord(); - Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected); - return isConnected; - } - - @Override - public void disconnect() { - //TODO(b/117129183): disconnected last select device - } - /** * Get current CachedBluetoothDevice */ diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java index 12d054e307ba..d84788b2739f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java @@ -108,9 +108,9 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName() + ", is connected : " + cachedDevice.isConnected() - + ", is preferred : " + a2dpProfile.isPreferred(device)); + + ", is enabled : " + a2dpProfile.isEnabled(device)); - if (a2dpProfile.isPreferred(device) + if (a2dpProfile.isEnabled(device) && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { addMediaDevice(cachedDevice); } @@ -143,9 +143,9 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName() + ", is connected : " + cachedDevice.isConnected() - + ", is preferred : " + hapProfile.isPreferred(device)); + + ", is enabled : " + hapProfile.isEnabled(device)); - if (hapProfile.isPreferred(device) + if (hapProfile.isEnabled(device) && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { addMediaDevice(cachedDevice); } @@ -157,7 +157,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall private void addMediaDevice(CachedBluetoothDevice cachedDevice) { MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice)); if (mediaDevice == null) { - mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice); + mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, null, null, null); cachedDevice.registerCallback(mDeviceAttributeChangeCallback); mLastAddedDevice = mediaDevice; mMediaDevices.add(mediaDevice); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 99c7dcf52818..b9081f241a91 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,9 +16,12 @@ package com.android.settingslib.media; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.text.TextUtils; +import android.util.Log; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -30,16 +33,9 @@ public class InfoMediaDevice extends MediaDevice { private static final String TAG = "InfoMediaDevice"; - private final MediaRoute2Info mRouteInfo; - private final MediaRouter2Manager mRouterManager; - private final String mPackageName; - InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, MediaDeviceType.TYPE_CAST_DEVICE); - mRouterManager = routerManager; - mRouteInfo = info; - mPackageName = packageName; + super(context, MediaDeviceType.TYPE_CAST_DEVICE, routerManager, info, packageName); initDeviceRecord(); } @@ -67,18 +63,45 @@ public class InfoMediaDevice extends MediaDevice { } @Override - public boolean connect() { - setConnectedRecord(); - mRouterManager.selectRoute(mPackageName, mRouteInfo); - return true; + public void requestSetVolume(int volume) { + mRouterManager.requestSetVolume(mRouteInfo, volume); + } + + @Override + public int getMaxVolume() { + return mRouteInfo.getVolumeMax(); } @Override - public void disconnect() { - //TODO(b/144535188): disconnected last select device + public int getCurrentVolume() { + return mRouteInfo.getVolume(); } @Override + public String getClientPackageName() { + return mRouteInfo.getClientPackageName(); + } + + @Override + public String getClientAppLabel() { + final String packageName = mRouteInfo.getClientPackageName(); + if (TextUtils.isEmpty(packageName)) { + Log.d(TAG, "Client package name is empty"); + return mContext.getResources().getString(R.string.unknown); + } + try { + final PackageManager packageManager = mContext.getPackageManager(); + final String appLabel = packageManager.getApplicationLabel( + packageManager.getApplicationInfo(packageName, 0)).toString(); + if (!TextUtils.isEmpty(appLabel)) { + return appLabel; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "unable to find " + packageName); + } + return mContext.getResources().getString(R.string.unknown); + } + public boolean isConnected() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index e008cd038317..e91096760180 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -15,13 +15,23 @@ */ package com.android.settingslib.media; +import static android.media.MediaRoute2Info.DEVICE_TYPE_BLUETOOTH; +import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV; +import static android.media.MediaRoute2Info.DEVICE_TYPE_UNKNOWN; + import android.app.Notification; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RoutingSessionInfo; import android.text.TextUtils; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.util.List; import java.util.concurrent.Executor; @@ -40,15 +50,19 @@ public class InfoMediaManager extends MediaManager { final Executor mExecutor = Executors.newSingleThreadExecutor(); @VisibleForTesting MediaRouter2Manager mRouterManager; + @VisibleForTesting + String mPackageName; - private String mPackageName; private MediaDevice mCurrentConnectedDevice; + private LocalBluetoothManager mBluetoothManager; - public InfoMediaManager(Context context, String packageName, Notification notification) { + public InfoMediaManager(Context context, String packageName, Notification notification, + LocalBluetoothManager localBluetoothManager) { super(context, notification); mRouterManager = MediaRouter2Manager.getInstance(context); - if (packageName != null) { + mBluetoothManager = localBluetoothManager; + if (!TextUtils.isEmpty(packageName)) { mPackageName = packageName; } } @@ -57,6 +71,7 @@ public class InfoMediaManager extends MediaManager { public void startScan() { mMediaDevices.clear(); mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); + refreshDevices(); } @VisibleForTesting @@ -79,22 +94,88 @@ public class InfoMediaManager extends MediaManager { return mCurrentConnectedDevice; } - class RouterManagerCallback extends MediaRouter2Manager.Callback { + private void refreshDevices() { + mMediaDevices.clear(); + mCurrentConnectedDevice = null; + if (TextUtils.isEmpty(mPackageName)) { + buildAllRoutes(); + } else { + buildAvailableRoutes(); + } + dispatchDeviceListAdded(); + } + + private void buildAllRoutes() { + for (MediaRoute2Info route : mRouterManager.getAllRoutes()) { + addMediaDevice(route); + } + } - private void refreshDevices() { - mMediaDevices.clear(); - mCurrentConnectedDevice = null; - for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) { - final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, route, - mPackageName); - if (TextUtils.equals(route.getClientPackageName(), mPackageName)) { - mCurrentConnectedDevice = device; + private void buildAvailableRoutes() { + for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) { + addMediaDevice(route); + } + } + + private void addMediaDevice(MediaRoute2Info route) { + final int deviceType = route.getDeviceType(); + MediaDevice mediaDevice = null; + switch (deviceType) { + case DEVICE_TYPE_UNKNOWN: + //TODO(b/148765806): use correct device type once api is ready. + final String defaultRoute = "DEFAULT_ROUTE"; + if (TextUtils.equals(defaultRoute, route.getOriginalId())) { + mediaDevice = + new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName); + } else { + mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route, + mPackageName); + if (!TextUtils.isEmpty(mPackageName) + && TextUtils.equals(route.getClientPackageName(), mPackageName)) { + mCurrentConnectedDevice = mediaDevice; + } } - mMediaDevices.add(device); - } - dispatchDeviceListAdded(); + break; + case DEVICE_TYPE_REMOTE_TV: + break; + case DEVICE_TYPE_BLUETOOTH: + final BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId()); + final CachedBluetoothDevice cachedDevice = + mBluetoothManager.getCachedDeviceManager().findDevice(device); + mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName); + break; + default: + Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType); + break; + } + if (mediaDevice != null) { + mMediaDevices.add(mediaDevice); + } + } + + /** + * Transfer MediaDevice for media without package name. + */ + public boolean connectDeviceWithoutPackageName(MediaDevice device) { + boolean isConnected = false; + final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions(); + if (infos.size() > 0) { + final RoutingSessionInfo info = infos.get(0); + final MediaRouter2Manager.RoutingController controller = + mRouterManager.getControllerForSession(info); + + controller.transferToRoute(device.mRouteInfo); + isConnected = true; + } + return isConnected; + } + + class RouterManagerCallback extends MediaRouter2Manager.Callback { + @Override public void onRoutesAdded(List<MediaRoute2Info> routes) { refreshDevices(); @@ -106,5 +187,10 @@ public class InfoMediaManager extends MediaManager { refreshDevices(); } } + + @Override + public void onRoutesChanged(List<MediaRoute2Info> routes) { + refreshDevices(); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index e85a102294d8..a1342ecdcfa7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -58,7 +58,6 @@ public class LocalMediaManager implements BluetoothCallback { final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback(); private Context mContext; - private BluetoothMediaManager mBluetoothMediaManager; private LocalBluetoothManager mLocalBluetoothManager; private InfoMediaManager mInfoMediaManager; private String mPackageName; @@ -98,18 +97,18 @@ public class LocalMediaManager implements BluetoothCallback { return; } - mBluetoothMediaManager = - new BluetoothMediaManager(context, mLocalBluetoothManager, notification); - mInfoMediaManager = new InfoMediaManager(context, packageName, notification); + mInfoMediaManager = + new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager); } @VisibleForTesting LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager, - BluetoothMediaManager bluetoothMediaManager, InfoMediaManager infoMediaManager) { + InfoMediaManager infoMediaManager, String packageName) { mContext = context; mLocalBluetoothManager = localBluetoothManager; - mBluetoothMediaManager = bluetoothMediaManager; mInfoMediaManager = infoMediaManager; + mPackageName = packageName; + } /** @@ -136,7 +135,12 @@ public class LocalMediaManager implements BluetoothCallback { mCurrentConnectedDevice.disconnect(); } - final boolean isConnected = device.connect(); + boolean isConnected = false; + if (TextUtils.isEmpty(mPackageName)) { + isConnected = mInfoMediaManager.connectDeviceWithoutPackageName(device); + } else { + isConnected = device.connect(); + } if (isConnected) { mCurrentConnectedDevice = device; } @@ -160,29 +164,8 @@ public class LocalMediaManager implements BluetoothCallback { */ public void startScan() { mMediaDevices.clear(); - mBluetoothMediaManager.registerCallback(mMediaDeviceCallback); - mBluetoothMediaManager.startScan(); - if (!TextUtils.isEmpty(mPackageName)) { - mInfoMediaManager.registerCallback(mMediaDeviceCallback); - mInfoMediaManager.startScan(); - } - } - - private void addPhoneDeviceIfNecessary() { - // add phone device to list if there have any Bluetooth device and cast device. - if (mMediaDevices.size() > 0 && !mMediaDevices.contains(mPhoneDevice)) { - if (mPhoneDevice == null) { - mPhoneDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager); - } - mMediaDevices.add(mPhoneDevice); - } - } - - private void removePhoneMediaDeviceIfNecessary() { - // if PhoneMediaDevice is the last item in the list, remove it. - if (mMediaDevices.size() == 1 && mMediaDevices.contains(mPhoneDevice)) { - mMediaDevices.clear(); - } + mInfoMediaManager.registerCallback(mMediaDeviceCallback); + mInfoMediaManager.startScan(); } void dispatchDeviceListUpdate() { @@ -206,12 +189,8 @@ public class LocalMediaManager implements BluetoothCallback { * Stop scan MediaDevice */ public void stopScan() { - mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback); - mBluetoothMediaManager.stopScan(); - if (!TextUtils.isEmpty(mPackageName)) { - mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); - mInfoMediaManager.stopScan(); - } + mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); + mInfoMediaManager.stopScan(); } /** @@ -232,6 +211,22 @@ public class LocalMediaManager implements BluetoothCallback { } /** + * Find the MediaDevice from all media devices by id. + * + * @param id the unique id of MediaDevice + * @return MediaDevice + */ + public MediaDevice getMediaDeviceById(String id) { + for (MediaDevice mediaDevice : mMediaDevices) { + if (mediaDevice.getId().equals(id)) { + return mediaDevice; + } + } + Log.i(TAG, "Unable to find device " + id); + return null; + } + + /** * Find the current connected MediaDevice. * * @return MediaDevice @@ -240,15 +235,34 @@ public class LocalMediaManager implements BluetoothCallback { return mCurrentConnectedDevice; } + /** + * Find the active MediaDevice. + * + * @param type the media device type. + * @return MediaDevice list + */ + public List<MediaDevice> getActiveMediaDevice(@MediaDevice.MediaDeviceType int type) { + final List<MediaDevice> devices = new ArrayList<>(); + for (MediaDevice device : mMediaDevices) { + if (type == device.mType && device.getClientPackageName() != null) { + devices.add(device); + } + } + return devices; + } + private MediaDevice updateCurrentConnectedDevice() { + MediaDevice phoneMediaDevice = null; for (MediaDevice device : mMediaDevices) { if (device instanceof BluetoothMediaDevice) { if (isConnected(((BluetoothMediaDevice) device).getCachedDevice())) { return device; } + } else if (device instanceof PhoneMediaDevice) { + phoneMediaDevice = device; } } - return mMediaDevices.contains(mPhoneDevice) ? mPhoneDevice : null; + return mMediaDevices.contains(phoneMediaDevice) ? phoneMediaDevice : null; } private boolean isConnected(CachedBluetoothDevice device) { @@ -261,38 +275,24 @@ public class LocalMediaManager implements BluetoothCallback { public void onDeviceAdded(MediaDevice device) { if (!mMediaDevices.contains(device)) { mMediaDevices.add(device); - addPhoneDeviceIfNecessary(); dispatchDeviceListUpdate(); } } @Override public void onDeviceListAdded(List<MediaDevice> devices) { - for (MediaDevice device : devices) { - if (getMediaDeviceById(mMediaDevices, device.getId()) == null) { - mMediaDevices.add(device); - } - } - addPhoneDeviceIfNecessary(); + mMediaDevices.clear(); + mMediaDevices.addAll(devices); final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice(); mCurrentConnectedDevice = infoMediaDevice != null ? infoMediaDevice : updateCurrentConnectedDevice(); - updatePhoneMediaDeviceSummary(); dispatchDeviceListUpdate(); } - private void updatePhoneMediaDeviceSummary() { - if (mPhoneDevice != null) { - ((PhoneMediaDevice) mPhoneDevice) - .updateSummary(mCurrentConnectedDevice == mPhoneDevice); - } - } - @Override public void onDeviceRemoved(MediaDevice device) { if (mMediaDevices.contains(device)) { mMediaDevices.remove(device); - removePhoneMediaDeviceIfNecessary(); dispatchDeviceListUpdate(); } } @@ -300,7 +300,6 @@ public class LocalMediaManager implements BluetoothCallback { @Override public void onDeviceListRemoved(List<MediaDevice> devices) { mMediaDevices.removeAll(devices); - removePhoneMediaDeviceIfNecessary(); dispatchDeviceListUpdate(); } @@ -313,7 +312,6 @@ public class LocalMediaManager implements BluetoothCallback { return; } mCurrentConnectedDevice = connectDevice; - updatePhoneMediaDeviceSummary(); dispatchDeviceAttributesChanged(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 53a852069478..580e086973d6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -17,9 +17,12 @@ package com.android.settingslib.media; import android.content.Context; import android.graphics.drawable.Drawable; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import android.text.TextUtils; import androidx.annotation.IntDef; +import androidx.annotation.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -40,14 +43,23 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { int TYPE_BLUETOOTH_DEVICE = 3; } + @VisibleForTesting + int mType; + private int mConnectedRecord; - protected Context mContext; - protected int mType; + protected final Context mContext; + protected final MediaRoute2Info mRouteInfo; + protected final MediaRouter2Manager mRouterManager; + protected final String mPackageName; - MediaDevice(Context context, @MediaDeviceType int type) { + MediaDevice(Context context, @MediaDeviceType int type, MediaRouter2Manager routerManager, + MediaRoute2Info info, String packageName) { mType = type; mContext = context; + mRouteInfo = info; + mRouterManager = routerManager; + mPackageName = packageName; } void initDeviceRecord() { @@ -83,13 +95,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { */ public abstract String getId(); - /** - * Transfer MediaDevice for media - * - * @return result of transfer media - */ - public abstract boolean connect(); - void setConnectedRecord() { mConnectedRecord++; ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(), @@ -97,11 +102,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { } /** - * Stop transfer MediaDevice - */ - public abstract void disconnect(); - - /** * According the MediaDevice type to check whether we are connected to this MediaDevice. * * @return Whether it is connected. @@ -109,6 +109,76 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { public abstract boolean isConnected(); /** + * Request to set volume. + * + * @param volume is the new value. + */ + public void requestSetVolume(int volume) { + } + + /** + * Get max volume from MediaDevice. + * + * @return max volume. + */ + public int getMaxVolume() { + return 100; + } + + /** + * Get current volume from MediaDevice. + * + * @return current volume. + */ + public int getCurrentVolume() { + return 0; + } + + /** + * Get application package name. + * + * @return package name. + */ + public String getClientPackageName() { + return null; + } + + /** + * Get application label from MediaDevice. + * + * @return application label. + */ + public String getClientAppLabel() { + return null; + } + + /** + * Get application label from MediaDevice. + * + * @return application label. + */ + public int getDeviceType() { + return mType; + } + + /** + * Transfer MediaDevice for media + * + * @return result of transfer media + */ + public boolean connect() { + setConnectedRecord(); + mRouterManager.selectRoute(mPackageName, mRouteInfo); + return true; + } + + /** + * Stop transfer MediaDevice + */ + public void disconnect() { + } + + /** * Rules: * 1. If there is one of the connected devices identified as a carkit, this carkit will * be always on the top of the device list. Rule 2 and Rule 3 can’t overrule this rule. diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java index 248b118f6ece..f341bf17edd3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java @@ -32,6 +32,16 @@ public class MediaOutputSliceConstants { public static final String KEY_REMOTE_MEDIA = "remote_media"; /** + * Key for the {@link android.media.session.MediaSession.Token}. + */ + public static final String KEY_MEDIA_SESSION_TOKEN = "key_media_session_token"; + + /** + * Key for the {@link android.media.RoutingSessionInfo#getId()} + */ + public static final String KEY_SESSION_INFO_ID = "key_session_info_id"; + + /** * Activity Action: Show a settings dialog containing {@link MediaDevice} to transfer media. */ public static final String ACTION_MEDIA_OUTPUT = diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index af91c3464194..166fbaa2a337 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -17,14 +17,11 @@ package com.android.settingslib.media; import android.content.Context; import android.graphics.drawable.Drawable; -import android.util.Log; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import com.android.settingslib.R; -import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothUtils; -import com.android.settingslib.bluetooth.HearingAidProfile; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; /** * PhoneMediaDevice extends MediaDevice to represents Phone device. @@ -35,15 +32,12 @@ public class PhoneMediaDevice extends MediaDevice { public static final String ID = "phone_media_device_id_1"; - private LocalBluetoothProfileManager mProfileManager; - private LocalBluetoothManager mLocalBluetoothManager; private String mSummary = ""; - PhoneMediaDevice(Context context, LocalBluetoothManager localBluetoothManager) { - super(context, MediaDeviceType.TYPE_PHONE_DEVICE); + PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, + String packageName) { + super(context, MediaDeviceType.TYPE_PHONE_DEVICE, routerManager, info, packageName); - mLocalBluetoothManager = localBluetoothManager; - mProfileManager = mLocalBluetoothManager.getProfileManager(); initDeviceRecord(); } @@ -69,32 +63,6 @@ public class PhoneMediaDevice extends MediaDevice { } @Override - public boolean connect() { - final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile(); - final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); - - // Some device may not have HearingAidProfile, consider all situation to set active device. - boolean isConnected = false; - if (hapProfile != null && a2dpProfile != null) { - isConnected = hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null); - } else if (a2dpProfile != null) { - isConnected = a2dpProfile.setActiveDevice(null); - } else if (hapProfile != null) { - isConnected = hapProfile.setActiveDevice(null); - } - updateSummary(isConnected); - setConnectedRecord(); - - Log.d(TAG, "connect() device : " + getName() + ", is selected : " + isConnected); - return isConnected; - } - - @Override - public void disconnect() { - updateSummary(false); - } - - @Override public boolean isConnected() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 84dde05afb2e..954eb9bf3cc4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -16,6 +16,9 @@ package com.android.settingslib.wifi; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED; + import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.Nullable; @@ -178,6 +181,7 @@ public class AccessPoint implements Comparable<AccessPoint> { static final String KEY_SCANRESULTS = "key_scanresults"; static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache"; static final String KEY_CONFIG = "key_config"; + static final String KEY_PASSPOINT_UNIQUE_ID = "key_passpoint_unique_id"; static final String KEY_FQDN = "key_fqdn"; static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name"; static final String KEY_EAPTYPE = "eap_psktype"; @@ -214,7 +218,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; public static final String KEY_PREFIX_AP = "AP:"; - public static final String KEY_PREFIX_FQDN = "FQDN:"; + public static final String KEY_PREFIX_PASSPOINT_UNIQUE_ID = "PASSPOINT:"; public static final String KEY_PREFIX_OSU = "OSU:"; private final Context mContext; @@ -247,6 +251,7 @@ public class AccessPoint implements Comparable<AccessPoint> { * Information associated with the {@link PasspointConfiguration}. Only maintaining * the relevant info to preserve spaces. */ + private String mPasspointUniqueId; private String mFqdn; private String mProviderFriendlyName; private boolean mIsRoaming = false; @@ -305,6 +310,9 @@ public class AccessPoint implements Comparable<AccessPoint> { mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore); } } + if (savedState.containsKey(KEY_PASSPOINT_UNIQUE_ID)) { + mPasspointUniqueId = savedState.getString(KEY_PASSPOINT_UNIQUE_ID); + } if (savedState.containsKey(KEY_FQDN)) { mFqdn = savedState.getString(KEY_FQDN); } @@ -348,6 +356,7 @@ public class AccessPoint implements Comparable<AccessPoint> { */ public AccessPoint(Context context, PasspointConfiguration config) { mContext = context; + mPasspointUniqueId = config.getUniqueId(); mFqdn = config.getHomeSp().getFqdn(); mProviderFriendlyName = config.getHomeSp().getFriendlyName(); mSubscriptionExpirationTimeInMillis = config.getSubscriptionExpirationTimeInMillis(); @@ -368,6 +377,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mContext = context; networkId = config.networkId; mConfig = config; + mPasspointUniqueId = config.getKey(); mFqdn = config.FQDN; setScanResultsPasspoint(homeScans, roamingScans); updateKey(); @@ -404,7 +414,7 @@ public class AccessPoint implements Comparable<AccessPoint> { if (isPasspoint()) { mKey = getKey(mConfig); } else if (isPasspointConfig()) { - mKey = getKey(mFqdn); + mKey = getKey(mPasspointUniqueId); } else if (isOsuProvider()) { mKey = getKey(mOsuProvider); } else { // Non-Passpoint AP @@ -654,7 +664,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } } } - return oldMetering == mIsScoredNetworkMetered; + return oldMetering != mIsScoredNetworkMetered; } /** @@ -674,19 +684,19 @@ public class AccessPoint implements Comparable<AccessPoint> { */ public static String getKey(WifiConfiguration config) { if (config.isPasspoint()) { - return getKey(config.FQDN); + return getKey(config.getKey()); } else { return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config)); } } /** - * Returns the AccessPoint key corresponding to a Passpoint network by its FQDN. + * Returns the AccessPoint key corresponding to a Passpoint network by its unique identifier. */ - public static String getKey(String fqdn) { + public static String getKey(String passpointUniqueId) { return new StringBuilder() - .append(KEY_PREFIX_FQDN) - .append(fqdn).toString(); + .append(KEY_PREFIX_PASSPOINT_UNIQUE_ID) + .append(passpointUniqueId).toString(); } /** @@ -763,7 +773,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public boolean matches(WifiConfiguration config) { if (config.isPasspoint()) { - return (isPasspoint() && config.FQDN.equals(mConfig.FQDN)); + return (isPasspoint() && config.getKey().equals(mConfig.getKey())); } if (!ssid.equals(removeDoubleQuotes(config.SSID)) @@ -1049,7 +1059,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public String getConfigName() { if (mConfig != null && mConfig.isPasspoint()) { return mConfig.providerFriendlyName; - } else if (mFqdn != null) { + } else if (mPasspointUniqueId != null) { return mProviderFriendlyName; } else { return ssid; @@ -1144,11 +1154,15 @@ public class AccessPoint implements Comparable<AccessPoint> { mInfo != null ? mInfo.getRequestingPackageName() : null)); } else { // not active if (mConfig != null && mConfig.hasNoInternetAccess()) { - int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled() + int messageID = + mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus() + == NETWORK_SELECTION_PERMANENTLY_DISABLED ? R.string.wifi_no_internet_no_reconnect : R.string.wifi_no_internet; summary.append(mContext.getString(messageID)); - } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) { + } else if (mConfig != null + && (mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus() + != NETWORK_SELECTION_ENABLED)) { WifiConfiguration.NetworkSelectionStatus networkStatus = mConfig.getNetworkSelectionStatus(); switch (networkStatus.getNetworkSelectionDisableReason()) { @@ -1170,8 +1184,8 @@ public class AccessPoint implements Comparable<AccessPoint> { } else { // In range, not disabled. if (mConfig != null) { // Is saved network // Last attempt to connect to this failed. Show reason why - switch (mConfig.recentFailure.getAssociationStatus()) { - case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA: + switch (mConfig.getRecentFailureReason()) { + case WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA: summary.append(mContext.getString( R.string.wifi_ap_unable_to_handle_new_sta)); break; @@ -1247,7 +1261,7 @@ public class AccessPoint implements Comparable<AccessPoint> { * Return true if this AccessPoint represents a Passpoint provider configuration. */ public boolean isPasspointConfig() { - return mFqdn != null && mConfig == null; + return mPasspointUniqueId != null && mConfig == null; } /** @@ -1303,8 +1317,12 @@ public class AccessPoint implements Comparable<AccessPoint> { if (info.isOsuAp() || mOsuStatus != null) { return (info.isOsuAp() && mOsuStatus != null); } else if (info.isPasspointAp() || isPasspoint()) { + // TODO: Use TextUtils.equals(info.getPasspointUniqueId(), mConfig.getKey()) when API + // is available return (info.isPasspointAp() && isPasspoint() - && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN)); + && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN) + && TextUtils.equals(info.getPasspointProviderFriendlyName(), + mConfig.providerFriendlyName)); } if (networkId != WifiConfiguration.INVALID_NETWORK_ID) { @@ -1370,6 +1388,9 @@ public class AccessPoint implements Comparable<AccessPoint> { if (mNetworkInfo != null) { savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo); } + if (mPasspointUniqueId != null) { + savedState.putString(KEY_PASSPOINT_UNIQUE_ID, mPasspointUniqueId); + } if (mFqdn != null) { savedState.putString(KEY_FQDN, mFqdn); } @@ -1942,11 +1963,11 @@ public class AccessPoint implements Comparable<AccessPoint> { return; } - String fqdn = passpointConfig.getHomeSp().getFqdn(); + String uniqueId = passpointConfig.getUniqueId(); for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing : wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) { WifiConfiguration config = pairing.first; - if (TextUtils.equals(config.FQDN, fqdn)) { + if (TextUtils.equals(config.getKey(), uniqueId)) { List<ScanResult> homeScans = pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); List<ScanResult> roamingScans = diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java index f21e466dd8ab..2fb2481ac117 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java @@ -84,7 +84,7 @@ public class TestAccessPointBuilder { bundle.putParcelable(AccessPoint.KEY_NETWORKINFO, mNetworkInfo); bundle.putParcelable(AccessPoint.KEY_WIFIINFO, mWifiInfo); if (mFqdn != null) { - bundle.putString(AccessPoint.KEY_FQDN, mFqdn); + bundle.putString(AccessPoint.KEY_PASSPOINT_UNIQUE_ID, mFqdn); } if (mProviderFriendlyName != null) { bundle.putString(AccessPoint.KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 26abf715369c..586c154179dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -606,7 +606,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values()); - // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN. + // Add a unique Passpoint AccessPoint for each Passpoint profile's unique identifier. accessPoints.addAll(updatePasspointAccessPoints( mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints)); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index 78ccba02fb04..d23364952357 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -16,9 +16,13 @@ package com.android.settingslib.wifi; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason; + import android.content.Context; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import android.net.wifi.WifiInfo; import android.os.SystemClock; @@ -41,7 +45,9 @@ public class WifiUtils { summary.append(" f=" + Integer.toString(info.getFrequency())); } summary.append(" " + getVisibilityStatus(accessPoint)); - if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) { + if (config != null + && (config.getNetworkSelectionStatus().getNetworkSelectionStatus() + != NETWORK_SELECTION_ENABLED)) { summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString()); if (config.getNetworkSelectionStatus().getDisableTime() > 0) { long now = System.currentTimeMillis(); @@ -58,15 +64,13 @@ public class WifiUtils { } if (config != null) { - WifiConfiguration.NetworkSelectionStatus networkStatus = - config.getNetworkSelectionStatus(); - for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE; - index < WifiConfiguration.NetworkSelectionStatus - .NETWORK_SELECTION_DISABLED_MAX; index++) { - if (networkStatus.getDisableReasonCounter(index) != 0) { - summary.append(" " + WifiConfiguration.NetworkSelectionStatus - .getNetworkDisableReasonString(index) + "=" - + networkStatus.getDisableReasonCounter(index)); + NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); + for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) { + if (networkStatus.getDisableReasonCounter(reason) != 0) { + summary.append(" ") + .append(NetworkSelectionStatus.getNetworkDisableReasonString(reason)) + .append("=") + .append(networkStatus.getDisableReasonCounter(reason)); } } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 42f3cbb04cf8..bcabec858487 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -465,6 +465,8 @@ public class AccessPointTest { WifiConfiguration.NetworkSelectionStatus status = mock(WifiConfiguration.NetworkSelectionStatus.class); when(configuration.getNetworkSelectionStatus()).thenReturn(status); + when(status.getNetworkSelectionStatus()).thenReturn( + WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED); when(status.getNetworkSelectionDisableReason()).thenReturn( WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD); AccessPoint ap = new AccessPoint(mContext, configuration); @@ -1370,13 +1372,13 @@ public class AccessPointTest { public void testOsuAccessPointSummary_showsProvisioningUpdates() { OsuProvider provider = createOsuProvider(); Context spyContext = spy(new ContextWrapper(mContext)); - AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider, - mScanResults); + when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>(); osuProviderConfigMap.put(provider, null); - when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); when(mMockWifiManager.getMatchingPasspointConfigsForOsuProviders( Collections.singleton(provider))).thenReturn(osuProviderConfigMap); + AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider, + mScanResults); osuAccessPoint.setListener(mMockAccessPointListener); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 11829451f640..6307caf5e02b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.res.Resources; import android.location.LocationManager; import android.media.AudioManager; +import android.os.BatteryManager; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; @@ -122,12 +123,12 @@ public class UtilsTest { public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() { Resources resources = mock(Resources.class); when(resources.getInteger( - eq( - com.android - .internal - .R - .integer - .config_storageManagerDaystoRetainDefault))) + eq( + com.android + .internal + .R + .integer + .config_storageManagerDaystoRetainDefault))) .thenReturn(60); assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60); } @@ -147,7 +148,8 @@ public class UtilsTest { private static Map<String, Integer> map = new HashMap<>(); @Implementation - public static boolean putIntForUser(ContentResolver cr, String name, int value, int userHandle) { + public static boolean putIntForUser(ContentResolver cr, String name, int value, + int userHandle) { map.put(name, value); return true; } @@ -312,4 +314,33 @@ public class UtilsTest { assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo( ServiceState.STATE_OUT_OF_SERVICE); } + + @Test + public void getBatteryStatus_statusIsFull_returnFullString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_full)); + } + + @Test + public void getBatteryStatus_batteryLevelIs100_returnFullString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_FULL); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_full)); + } + + @Test + public void getBatteryStatus_batteryLevel99_returnChargingString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_CHARGING); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + resources.getString(R.string.battery_info_status_charging)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java index ccb6646cf683..9bb2f22ddbcf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothA2dpSink; @@ -68,18 +64,6 @@ public class A2dpSinkProfileTest { } @Test - public void connect_shouldConnectBluetoothA2dpSink() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothA2dpSink() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java index 91807609df1a..d121e0b2d2fb 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class HfpClientProfileTest { } @Test - public void connect_shouldConnectBluetoothHeadsetClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothHeadsetClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java index f38af70c7498..3665d9c10165 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java @@ -18,7 +18,6 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -65,17 +64,6 @@ public class HidDeviceProfileTest { } @Test - public void connect_shouldReturnFalse() { - assertThat(mProfile.connect(mBluetoothDevice)).isFalse(); - } - - @Test - public void disconnect_shouldDisconnectBluetoothHidDevice() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java index 1425c381256b..25031a62294c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class MapClientProfileTest { } @Test - public void connect_shouldConnectBluetoothMapClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothMapClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java index 15f560bef73e..4305a3bc25a3 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -68,18 +64,6 @@ public class PbapClientProfileTest { } @Test - public void connect_shouldConnectBluetoothPbapClient() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothPbapClient() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java index 4f978a822890..e460eaf16bbf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java @@ -16,12 +16,8 @@ package com.android.settingslib.bluetooth; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; -import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; @@ -67,18 +63,6 @@ public class SapProfileTest { } @Test - public void connect_shouldConnectBluetoothSap() { - mProfile.connect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); - } - - @Test - public void disconnect_shouldDisconnectBluetoothSap() { - mProfile.disconnect(mBluetoothDevice); - verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); - } - - @Test public void getConnectionStatus_shouldReturnConnectionState() { when(mService.getConnectionState(mBluetoothDevice)). thenReturn(BluetoothProfile.STATE_CONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java index 4b5e9097b3fe..e87461f85762 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java @@ -28,8 +28,11 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; @RunWith(RobolectricTestRunner.class) public class LicenseHtmlGeneratorFromXmlTest { @@ -68,6 +71,7 @@ public class LicenseHtmlGeneratorFromXmlTest { private static final String HTML_BODY_STRING = "<li><a href=\"#id0\">/file0</a></li>\n" + + "<li><a href=\"#id1\">/file0</a></li>\n" + "<li><a href=\"#id0\">/file1</a></li>\n" + "</ul>\n" + "</div><!-- table of contents -->\n" @@ -82,6 +86,15 @@ public class LicenseHtmlGeneratorFromXmlTest { + "license content #0\n" + "</pre><!-- license-text -->\n" + "</td></tr><!-- same-license -->\n" + + "<tr id=\"id1\"><td class=\"same-license\">\n" + + "<div class=\"label\">Notices for file(s):</div>\n" + + "<div class=\"file-list\">\n" + + "/file0 <br/>\n" + + "</div><!-- file-list -->\n" + + "<pre class=\"license-text\">\n" + + "license content #1\n" + + "</pre><!-- license-text -->\n" + + "</td></tr><!-- same-license -->\n" + "</table></body></html>\n"; private static final String EXPECTED_HTML_STRING = HTML_HEAD_STRING + HTML_BODY_STRING; @@ -91,22 +104,22 @@ public class LicenseHtmlGeneratorFromXmlTest { @Test public void testParseValidXmlStream() throws XmlPullParserException, IOException { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); LicenseHtmlGeneratorFromXml.parse( new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())), fileNameToContentIdMap, contentIdToFileContentMap); assertThat(fileNameToContentIdMap.size()).isEqualTo(2); - assertThat(fileNameToContentIdMap.get("/file0")).isEqualTo("0"); - assertThat(fileNameToContentIdMap.get("/file1")).isEqualTo("0"); + assertThat(fileNameToContentIdMap.get("/file0")).containsExactly("0"); + assertThat(fileNameToContentIdMap.get("/file1")).containsExactly("0"); assertThat(contentIdToFileContentMap.size()).isEqualTo(1); assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0"); } @Test(expected = XmlPullParserException.class) public void testParseInvalidXmlStream() throws XmlPullParserException, IOException { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); LicenseHtmlGeneratorFromXml.parse( @@ -116,12 +129,13 @@ public class LicenseHtmlGeneratorFromXmlTest { @Test public void testGenerateHtml() { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); - fileNameToContentIdMap.put("/file0", "0"); - fileNameToContentIdMap.put("/file1", "0"); + fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1"))); + fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0"))); contentIdToFileContentMap.put("0", "license content #0"); + contentIdToFileContentMap.put("1", "license content #1"); StringWriter output = new StringWriter(); LicenseHtmlGeneratorFromXml.generateHtml( @@ -131,12 +145,13 @@ public class LicenseHtmlGeneratorFromXmlTest { @Test public void testGenerateHtmlWithCustomHeading() { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); - fileNameToContentIdMap.put("/file0", "0"); - fileNameToContentIdMap.put("/file1", "0"); + fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1"))); + fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0"))); contentIdToFileContentMap.put("0", "license content #0"); + contentIdToFileContentMap.put("1", "license content #1"); StringWriter output = new StringWriter(); LicenseHtmlGeneratorFromXml.generateHtml( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java index e0e2fd6860ff..a39bcb7f08f6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java @@ -51,21 +51,7 @@ public class BluetoothMediaDeviceTest { when(mDevice.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true); when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true); - mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice); - } - - @Test - public void connect_setActiveSuccess_isConnectedReturnTrue() { - when(mDevice.setActive()).thenReturn(true); - - assertThat(mBluetoothMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_setActiveFail_isConnectedReturnFalse() { - when(mDevice.setActive()).thenReturn(false); - - assertThat(mBluetoothMediaDevice.connect()).isFalse(); + mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null, null); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java index f27cef9620c5..0ee5ea8a2eed 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java @@ -96,7 +96,7 @@ public class BluetoothMediaManagerTest { when(mA2dpProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); @@ -113,7 +113,7 @@ public class BluetoothMediaManagerTest { when(mA2dpProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); - when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); @@ -141,7 +141,7 @@ public class BluetoothMediaManagerTest { when(mHapProfile.getConnectableDevices()).thenReturn(devices); when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mHapProfile.isPreferred(bluetoothDevice)).thenReturn(true); + when(mHapProfile.isEnabled(bluetoothDevice)).thenReturn(true); assertThat(mMediaManager.mMediaDevices).isEmpty(); mMediaManager.startScan(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java index c9db0d13a7e7..04ceb2147c6e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java @@ -18,10 +18,12 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageStats; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; @@ -34,11 +36,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class InfoMediaDeviceTest { private static final String TEST_PACKAGE_NAME = "com.test.packagename"; + private static final String TEST_PACKAGE_NAME2 = "com.test.packagename2"; private static final String TEST_ID = "test_id"; private static final String TEST_NAME = "test_name"; @@ -50,11 +55,24 @@ public class InfoMediaDeviceTest { private Context mContext; private InfoMediaDevice mInfoMediaDevice; + private ShadowPackageManager mShadowPackageManager; + private ApplicationInfo mAppInfo; + private PackageInfo mPackageInfo; + private PackageStats mPackageStats; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mAppInfo = new ApplicationInfo(); + mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; + mAppInfo.packageName = TEST_PACKAGE_NAME; + mAppInfo.name = TEST_NAME; + mPackageInfo = new PackageInfo(); + mPackageInfo.packageName = TEST_PACKAGE_NAME; + mPackageInfo.applicationInfo = mAppInfo; + mPackageStats = new PackageStats(TEST_PACKAGE_NAME); mInfoMediaDevice = new InfoMediaDevice(mContext, mRouterManager, mRouteInfo, TEST_PACKAGE_NAME); @@ -90,9 +108,30 @@ public class InfoMediaDeviceTest { } @Test - public void connect_shouldSelectRoute() { - mInfoMediaDevice.connect(); + public void getClientPackageName_returnPackageName() { + when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME); + } + + @Test + public void getClientAppLabel_matchedPackageName_returnLabel() { + when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); + + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + + assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(TEST_NAME); + } + + @Test + public void getClientAppLabel_noMatchedPackageName_returnDefault() { + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2); - verify(mRouterManager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo); + assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 67f6dd903841..05f5fa0afc9e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -25,6 +25,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RoutingSessionInfo; + +import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; import org.junit.Test; @@ -45,6 +48,8 @@ public class InfoMediaManagerTest { @Mock private MediaRouter2Manager mRouterManager; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; private InfoMediaManager mInfoMediaManager; private Context mContext; @@ -54,7 +59,8 @@ public class InfoMediaManagerTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mInfoMediaManager = new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null); + mInfoMediaManager = + new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager); mInfoMediaManager.mRouterManager = mRouterManager; } @@ -74,7 +80,7 @@ public class InfoMediaManagerTest { } @Test - public void onRouteAdded_shouldAddMediaDevice() { + public void onRouteAdded_getAvailableRoutes_shouldAddMediaDevice() { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); @@ -95,6 +101,27 @@ public class InfoMediaManagerTest { } @Test + public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAllRoutes()).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mPackageName = ""; + mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } + + @Test public void onControlCategoriesChanged_samePackageName_shouldAddMediaDevice() { final MediaRoute2Info info = mock(MediaRoute2Info.class); when(info.getId()).thenReturn(TEST_ID); @@ -121,4 +148,59 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.mMediaDevices).hasSize(0); } + + @Test + public void onRoutesChanged_getAvailableRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } + + @Test + public void onRoutesChanged_buildAllRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAllRoutes()).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mPackageName = ""; + mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } + + @Test + public void connectDeviceWithoutPackageName_noSession_returnFalse() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, info, + TEST_PACKAGE_NAME); + + final List<RoutingSessionInfo> infos = new ArrayList<>(); + + when(mRouterManager.getActiveSessions()).thenReturn(infos); + + assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index c780a64c2fb4..3d67ba053a45 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -80,7 +80,7 @@ public class LocalMediaManagerTest { when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, - mBluetoothMediaManager, mInfoMediaManager); + mInfoMediaManager, "com.test.packagename"); } @Test @@ -163,14 +163,14 @@ public class LocalMediaManagerTest { } @Test - public void onDeviceAdded_mediaDeviceAndPhoneDeviceNotExistInList_addBothDevice() { + public void onDeviceAdded_addDevice() { final MediaDevice device = mock(MediaDevice.class); assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceAdded(device); - assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); verify(mCallback).onDeviceListUpdate(any()); } @@ -206,7 +206,7 @@ public class LocalMediaManagerTest { } @Test - public void onDeviceListAdded_phoneDeviceNotExistInList_addPhoneDeviceAndDevicesList() { + public void onDeviceListAdded_addDevicesList() { final List<MediaDevice> devices = new ArrayList<>(); final MediaDevice device1 = mock(MediaDevice.class); final MediaDevice device2 = mock(MediaDevice.class); @@ -220,12 +220,12 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); - assertThat(mLocalMediaManager.mMediaDevices).hasSize(3); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); verify(mCallback).onDeviceListUpdate(any()); } @Test - public void onDeviceListAdded_phoneDeviceExistInList_addDeviceList() { + public void onDeviceListAdded_addDeviceList() { final List<MediaDevice> devices = new ArrayList<>(); final MediaDevice device1 = mock(MediaDevice.class); final MediaDevice device2 = mock(MediaDevice.class); @@ -245,12 +245,12 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); - assertThat(mLocalMediaManager.mMediaDevices).hasSize(4); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); verify(mCallback).onDeviceListUpdate(any()); } @Test - public void onDeviceRemoved_phoneDeviceIsLastDeviceAfterRemoveMediaDevice_removeBothDevice() { + public void onDeviceRemoved_removeDevice() { final MediaDevice device1 = mock(MediaDevice.class); mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class); mLocalMediaManager.mMediaDevices.add(device1); @@ -260,7 +260,7 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onDeviceRemoved(device1); - assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); + assertThat(mLocalMediaManager.mMediaDevices).hasSize(1); verify(mCallback).onDeviceListUpdate(any()); } @@ -298,7 +298,7 @@ public class LocalMediaManagerTest { } @Test - public void onDeviceListRemoved_phoneDeviceIsLastDeviceAfterRemoveDeviceList_removeAll() { + public void onDeviceListRemoved_removeAll() { final List<MediaDevice> devices = new ArrayList<>(); final MediaDevice device1 = mock(MediaDevice.class); final MediaDevice device2 = mock(MediaDevice.class); @@ -311,7 +311,8 @@ public class LocalMediaManagerTest { assertThat(mLocalMediaManager.mMediaDevices).hasSize(3); mLocalMediaManager.registerCallback(mCallback); - mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devices); + mLocalMediaManager.mMediaDeviceCallback + .onDeviceListRemoved(mLocalMediaManager.mMediaDevices); assertThat(mLocalMediaManager.mMediaDevices).isEmpty(); verify(mCallback).onDeviceListUpdate(any()); @@ -384,4 +385,38 @@ public class LocalMediaManagerTest { verify(mCallback).onDeviceAttributesChanged(); } + + @Test + public void getActiveMediaDevice_checkList() { + final List<MediaDevice> devices = new ArrayList<>(); + final MediaDevice device1 = mock(MediaDevice.class); + final MediaDevice device2 = mock(MediaDevice.class); + final MediaDevice device3 = mock(MediaDevice.class); + device1.mType = MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE; + device2.mType = MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE; + device3.mType = MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE; + when(device1.getClientPackageName()).thenReturn(TEST_DEVICE_ID_1); + when(device2.getClientPackageName()).thenReturn(TEST_DEVICE_ID_2); + when(device3.getClientPackageName()).thenReturn(TEST_DEVICE_ID_3); + when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); + when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); + when(device3.getId()).thenReturn(TEST_DEVICE_ID_3); + devices.add(device1); + devices.add(device2); + devices.add(device3); + mLocalMediaManager.registerCallback(mCallback); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); + + List<MediaDevice> activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE); + assertThat(activeDevices).containsExactly(device1); + + activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); + assertThat(activeDevices).containsExactly(device2); + + activeDevices = mLocalMediaManager.getActiveMediaDevice( + MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); + assertThat(activeDevices).containsExactly(device3); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index 02cb83e0281a..fb8b78b22055 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -17,6 +17,7 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; @@ -83,6 +84,14 @@ public class MediaDeviceTest { @Mock private MediaRoute2Info mRouteInfo3; @Mock + private MediaRoute2Info mBluetoothRouteInfo1; + @Mock + private MediaRoute2Info mBluetoothRouteInfo2; + @Mock + private MediaRoute2Info mBluetoothRouteInfo3; + @Mock + private MediaRoute2Info mPhoneRouteInfo; + @Mock private LocalBluetoothProfileManager mProfileManager; @Mock private HearingAidProfile mHapProfile; @@ -90,6 +99,8 @@ public class MediaDeviceTest { private A2dpProfile mA2dpProfile; @Mock private BluetoothDevice mDevice; + @Mock + private MediaRouter2Manager mMediaRouter2Manager; private BluetoothMediaDevice mBluetoothMediaDevice1; private BluetoothMediaDevice mBluetoothMediaDevice2; @@ -100,7 +111,6 @@ public class MediaDeviceTest { private InfoMediaDevice mInfoMediaDevice3; private List<MediaDevice> mMediaDevices = new ArrayList<>(); private PhoneMediaDevice mPhoneMediaDevice; - private MediaRouter2Manager mMediaRouter2Manager; @Before public void setUp() { @@ -133,17 +143,24 @@ public class MediaDeviceTest { when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice); - mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1); - mBluetoothMediaDevice2 = new BluetoothMediaDevice(mContext, mCachedDevice2); - mBluetoothMediaDevice3 = new BluetoothMediaDevice(mContext, mCachedDevice3); - mMediaRouter2Manager = MediaRouter2Manager.getInstance(mContext); + mBluetoothMediaDevice1 = + new BluetoothMediaDevice(mContext, mCachedDevice1, mMediaRouter2Manager, + mBluetoothRouteInfo1, TEST_PACKAGE_NAME); + mBluetoothMediaDevice2 = + new BluetoothMediaDevice(mContext, mCachedDevice2, mMediaRouter2Manager, + mBluetoothRouteInfo2, TEST_PACKAGE_NAME); + mBluetoothMediaDevice3 = + new BluetoothMediaDevice(mContext, mCachedDevice3, mMediaRouter2Manager, + mBluetoothRouteInfo3, TEST_PACKAGE_NAME); mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1, TEST_PACKAGE_NAME); mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2, TEST_PACKAGE_NAME); mInfoMediaDevice3 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo3, TEST_PACKAGE_NAME); - mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo, + TEST_PACKAGE_NAME); } @Test @@ -370,4 +387,11 @@ public class MediaDeviceTest { assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice1); assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice2); } + + @Test + public void connect_shouldSelectRoute() { + mInfoMediaDevice1.connect(); + + verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 0752dc03f397..db984fb8dc26 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -18,21 +18,13 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.when; - -import android.bluetooth.BluetoothDevice; import android.content.Context; import com.android.settingslib.R; -import com.android.settingslib.bluetooth.A2dpProfile; -import com.android.settingslib.bluetooth.HearingAidProfile; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -40,17 +32,6 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class PhoneMediaDeviceTest { - @Mock - private LocalBluetoothProfileManager mLocalProfileManager; - @Mock - private LocalBluetoothManager mLocalBluetoothManager; - @Mock - private HearingAidProfile mHapProfile; - @Mock - private A2dpProfile mA2dpProfile; - @Mock - private BluetoothDevice mDevice; - private Context mContext; private PhoneMediaDevice mPhoneMediaDevice; @@ -59,68 +40,8 @@ public class PhoneMediaDeviceTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager); - when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); - when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); - when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice); - - mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager); - } - - @Test - public void connect_phoneDeviceSetActiveSuccess_isConnectedReturnTrue() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(true); - when(mHapProfile.setActiveDevice(null)).thenReturn(true); - - assertThat(mPhoneMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_a2dpProfileSetActiveFail_isConnectedReturnFalse() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(false); - when(mHapProfile.setActiveDevice(null)).thenReturn(true); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); - } - - @Test - public void connect_hearingAidProfileSetActiveFail_isConnectedReturnFalse() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(true); - when(mHapProfile.setActiveDevice(null)).thenReturn(false); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); - } - - @Test - public void connect_hearingAidAndA2dpProfileSetActiveFail_isConnectedReturnFalse() { - when(mA2dpProfile.setActiveDevice(null)).thenReturn(false); - when(mHapProfile.setActiveDevice(null)).thenReturn(false); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); - } - - @Test - public void connect_hearingAidProfileIsNullAndA2dpProfileNotNull_isConnectedReturnTrue() { - when(mLocalProfileManager.getHearingAidProfile()).thenReturn(null); - - when(mA2dpProfile.setActiveDevice(null)).thenReturn(true); - assertThat(mPhoneMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_hearingAidProfileNotNullAndA2dpProfileIsNull_isConnectedReturnTrue() { - when(mLocalProfileManager.getA2dpProfile()).thenReturn(null); - - when(mHapProfile.setActiveDevice(null)).thenReturn(true); - assertThat(mPhoneMediaDevice.connect()).isTrue(); - } - - @Test - public void connect_hearingAidProfileAndA2dpProfileIsNull_isConnectedReturnFalse() { - when(mLocalProfileManager.getA2dpProfile()).thenReturn(null); - when(mLocalProfileManager.getHearingAidProfile()).thenReturn(null); - - assertThat(mPhoneMediaDevice.connect()).isFalse(); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, null, null, null); } @Test diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index cdf97285a16f..44864a61ade6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -45,6 +45,7 @@ import android.provider.settings.validators.Validator; import android.util.ArrayMap; import android.util.ArraySet; import android.util.BackupUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Slog; import android.view.Display; @@ -280,6 +281,16 @@ public class SettingsBackupAgent extends BackupAgentHelper { Settings.Secure.getMovedToGlobalSettings(movedToGlobal); Set<String> movedToSecure = getMovedToSecureSettings(); + Set<String> preservedGlobalSettings = getSettingsToPreserveInRestore( + Settings.Global.CONTENT_URI); + Set<String> preservedSecureSettings = getSettingsToPreserveInRestore( + Settings.Secure.CONTENT_URI); + Set<String> preservedSystemSettings = getSettingsToPreserveInRestore( + Settings.System.CONTENT_URI); + Set<String> preservedSettings = new HashSet<>(preservedGlobalSettings); + preservedSettings.addAll(preservedSecureSettings); + preservedSettings.addAll(preservedSystemSettings); + byte[] restoredWifiSupplicantData = null; byte[] restoredWifiIpConfigData = null; @@ -300,7 +311,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { case KEY_SYSTEM : restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal, movedToSecure, R.array.restore_blocked_system_settings, - dynamicBlockList); + dynamicBlockList, + preservedSystemSettings); mSettingsHelper.applyAudioSettings(); break; @@ -311,7 +323,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, null, R.array.restore_blocked_secure_settings, - dynamicBlockList); + dynamicBlockList, + preservedSecureSettings); break; case KEY_GLOBAL : @@ -321,7 +334,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { null, movedToSecure, R.array.restore_blocked_global_settings, - dynamicBlockList); + dynamicBlockList, + preservedGlobalSettings); break; case KEY_WIFI_SUPPLICANT : @@ -368,7 +382,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreDeviceSpecificConfig( restoredDeviceSpecificConfig, R.array.restore_blocked_device_specific_settings, - dynamicBlockList); + dynamicBlockList, + preservedSettings); break; default : @@ -418,7 +433,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { in.readFully(buffer, 0, nBytes); restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal, movedToSecure, R.array.restore_blocked_system_settings, - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); // secure settings nBytes = in.readInt(); @@ -432,7 +447,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, null, R.array.restore_blocked_secure_settings, - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); // Global only if sufficiently new if (version >= FULL_BACKUP_ADDED_GLOBAL) { @@ -443,7 +458,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal.clear(); // no redirection; this *is* the global namespace restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal, movedToSecure, R.array.restore_blocked_global_settings, - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); } // locale @@ -608,6 +623,40 @@ public class SettingsBackupAgent extends BackupAgentHelper { } /** + * Get names of the settings for which the current value should be preserved during restore. + */ + private Set<String> getSettingsToPreserveInRestore(Uri settingsUri) { + if (!FeatureFlagUtils.isEnabled(getBaseContext(), + FeatureFlagUtils.SETTINGS_DO_NOT_RESTORE_PRESERVED)) { + return Collections.emptySet(); + } + + Cursor cursor = getContentResolver().query(settingsUri, new String[] { + Settings.NameValueTable.NAME, Settings.NameValueTable.IS_PRESERVED_IN_RESTORE }, + /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null); + + if (!cursor.moveToFirst()) { + Slog.i(TAG, "No settings to be preserved in restore"); + return Collections.emptySet(); + } + + int nameIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME); + int isPreservedIndex = cursor.getColumnIndex( + Settings.NameValueTable.IS_PRESERVED_IN_RESTORE); + + Set<String> preservedSettings = new HashSet<>(); + while (!cursor.isAfterLast()) { + if (Boolean.parseBoolean(cursor.getString(isPreservedIndex))) { + preservedSettings.add(getQualifiedKeyForSetting(cursor.getString(nameIndex), + settingsUri)); + } + cursor.moveToNext(); + } + + return preservedSettings; + } + + /** * Serialize the owner info and other lock settings */ private byte[] getLockSettings(@UserIdInt int userId) { @@ -650,7 +699,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { HashSet<String> movedToGlobal, Set<String> movedToSecure, int blockedSettingsArrayId, - Set<String> dynamicBlockList) { + Set<String> dynamicBlockList, + Set<String> settingsToPreserve) { byte[] settings = new byte[data.getDataSize()]; try { data.readEntityData(settings, 0, settings.length); @@ -665,7 +715,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, movedToSecure, blockedSettingsArrayId, - dynamicBlockList); + dynamicBlockList, + settingsToPreserve); } private void restoreSettings( @@ -675,7 +726,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { HashSet<String> movedToGlobal, Set<String> movedToSecure, int blockedSettingsArrayId, - Set<String> dynamicBlockList) { + Set<String> dynamicBlockList, + Set<String> settingsToPreserve) { restoreSettings( settings, 0, @@ -684,10 +736,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToGlobal, movedToSecure, blockedSettingsArrayId, - dynamicBlockList); + dynamicBlockList, + settingsToPreserve); } - private void restoreSettings( + @VisibleForTesting + void restoreSettings( byte[] settings, int pos, int bytes, @@ -695,31 +749,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { HashSet<String> movedToGlobal, Set<String> movedToSecure, int blockedSettingsArrayId, - Set<String> dynamicBlockList) { + Set<String> dynamicBlockList, + Set<String> settingsToPreserve) { if (DEBUG) { Log.i(TAG, "restoreSettings: " + contentUri); } - // Figure out the white list and redirects to the global table. We restore anything - // in either the backup whitelist or the legacy-restore whitelist for this table. - final String[] whitelist; - Map<String, Validator> validators = null; - if (contentUri.equals(Settings.Secure.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP, - Settings.Secure.LEGACY_RESTORE_SETTINGS, - DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); - validators = SecureSettingsValidators.VALIDATORS; - } else if (contentUri.equals(Settings.System.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP, - Settings.System.LEGACY_RESTORE_SETTINGS); - validators = SystemSettingsValidators.VALIDATORS; - } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { - whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP, - Settings.Global.LEGACY_RESTORE_SETTINGS); - validators = GlobalSettingsValidators.VALIDATORS; - } else { - throw new IllegalArgumentException("Unknown URI: " + contentUri); - } + SettingsBackupWhitelist whitelist = getBackupWhitelist(contentUri); // Restore only the white list data. final ArrayMap<String, String> cachedEntries = new ArrayMap<>(); @@ -729,7 +765,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId); - for (String key : whitelist) { + for (String key : whitelist.mSettingsWhitelist) { boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key); if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri, key)) { Log.i( @@ -742,6 +778,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { continue; } + if (settingsToPreserve.contains(getQualifiedKeyForSetting(key, contentUri))) { + Log.i(TAG, "Skipping restore for setting " + key + " as it is marked as " + + "preserved"); + continue; + } + String value = null; boolean hasValueToRestore = false; if (cachedEntries.indexOfKey(key) >= 0) { @@ -775,7 +817,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { } // only restore the settings that have valid values - if (!isValidSettingValue(key, value, validators)) { + if (!isValidSettingValue(key, value, whitelist.mSettingsValidators)) { Log.w(TAG, "Attempted restore of " + key + " setting, but its value didn't pass" + " validation, value: " + value); continue; @@ -798,11 +840,42 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + @VisibleForTesting + SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) { + // Figure out the white list and redirects to the global table. We restore anything + // in either the backup whitelist or the legacy-restore whitelist for this table. + String[] whitelist; + Map<String, Validator> validators = null; + if (contentUri.equals(Settings.Secure.CONTENT_URI)) { + whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP, + Settings.Secure.LEGACY_RESTORE_SETTINGS, + DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); + validators = SecureSettingsValidators.VALIDATORS; + } else if (contentUri.equals(Settings.System.CONTENT_URI)) { + whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP, + Settings.System.LEGACY_RESTORE_SETTINGS); + validators = SystemSettingsValidators.VALIDATORS; + } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { + whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP, + Settings.Global.LEGACY_RESTORE_SETTINGS); + validators = GlobalSettingsValidators.VALIDATORS; + } else { + throw new IllegalArgumentException("Unknown URI: " + contentUri); + } + + return new SettingsBackupWhitelist(whitelist, validators); + } + private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) { String contentKey = Uri.withAppendedPath(areaUri, key).toString(); return dynamicBlockList.contains(contentKey); } + @VisibleForTesting + static String getQualifiedKeyForSetting(String settingName, Uri settingUri) { + return Uri.withAppendedPath(settingUri, settingName).toString(); + } + // There may be other sources of blocked settings, so I'm separating out this // code to make it easy to modify in the future. @VisibleForTesting @@ -1089,7 +1162,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { */ @VisibleForTesting boolean restoreDeviceSpecificConfig(byte[] data, int blockedSettingsArrayId, - Set<String> dynamicBlocklist) { + Set<String> dynamicBlocklist, Set<String> preservedSettings) { // We're using an AtomicInteger to wrap the position int and allow called methods to // modify it. AtomicInteger pos = new AtomicInteger(0); @@ -1108,7 +1181,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { null, null, blockedSettingsArrayId, - dynamicBlocklist); + dynamicBlocklist, + preservedSettings); updateWindowManagerIfNeeded(originalDensity); @@ -1240,4 +1314,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { | ((in[pos + 3] & 0xFF) << 0); return result; } + + /** + * Store the whitelist of settings to be backed up and validators for them. + */ + @VisibleForTesting + static class SettingsBackupWhitelist { + final String[] mSettingsWhitelist; + final Map<String, Validator> mSettingsValidators; + + + SettingsBackupWhitelist(String[] settingsWhitelist, + Map<String, Validator> settingsValidators) { + mSettingsWhitelist = settingsWhitelist; + mSettingsValidators = settingsValidators; + } + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 1bec826897c0..78b9f16ef355 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2185,13 +2185,14 @@ class SettingsProtoDumpUtil { Settings.Secure.NAVIGATION_MODE, SecureSettingsProto.NAVIGATION_MODE); + final long gestureNavToken = p.start(SecureSettingsProto.GESTURE_NAVIGATION); dumpSetting(s, p, Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_LEFT); - dumpSetting(s, p, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_RIGHT); + p.end(gestureNavToken); final long nfcPaymentToken = p.start(SecureSettingsProto.NFC_PAYMENT); dumpSetting(s, p, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 874e29940202..c969bfd193b5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -180,10 +180,17 @@ public class SettingsProvider extends ContentProvider { private static final int MUTATION_OPERATION_UPDATE = 3; private static final int MUTATION_OPERATION_RESET = 4; + private static final String[] LEGACY_SQL_COLUMNS = new String[] { + Settings.NameValueTable._ID, + Settings.NameValueTable.NAME, + Settings.NameValueTable.VALUE, + }; + private static final String[] ALL_COLUMNS = new String[] { Settings.NameValueTable._ID, Settings.NameValueTable.NAME, - Settings.NameValueTable.VALUE + Settings.NameValueTable.VALUE, + Settings.NameValueTable.IS_PRESERVED_IN_RESTORE, }; public static final int SETTINGS_TYPE_GLOBAL = SettingsState.SETTINGS_TYPE_GLOBAL; @@ -2353,6 +2360,10 @@ public class SettingsProvider extends ContentProvider { case Settings.NameValueTable.VALUE: { values[i] = setting.getValue(); } break; + + case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE: { + values[i] = String.valueOf(setting.isValuePreservedInRestore()); + } break; } } @@ -3097,7 +3108,7 @@ public class SettingsProvider extends ContentProvider { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(table); - Cursor cursor = queryBuilder.query(database, ALL_COLUMNS, + Cursor cursor = queryBuilder.query(database, LEGACY_SQL_COLUMNS, null, null, null, null, null); if (cursor == null) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index db18213a3599..cd62420f39ac 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -41,13 +41,13 @@ import android.util.AtomicFile; import android.util.Base64; import android.util.Slog; import android.util.SparseIntArray; -import android.util.StatsLog; import android.util.TimeUtils; import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; import libcore.io.IoUtils; @@ -425,12 +425,14 @@ final class SettingsState { } newState = oldState; } else { - newState = new Setting(name, value, makeDefault, packageName, tag); + newState = new Setting(name, value, makeDefault, packageName, tag, + forceNonSystemPackage); mSettings.put(name, newState); } - StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newState.value, oldValue, tag, - makeDefault, getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__UPDATED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, value, newState.value, + oldValue, tag, makeDefault, getUserIdFromKey(mKey), + FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED); addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState); @@ -489,9 +491,9 @@ final class SettingsState { if (key.startsWith(prefix) && !keyValues.containsKey(key)) { Setting oldState = mSettings.remove(key); - StatsLog.write(StatsLog.SETTING_CHANGED, key, /* value= */ "", /* newValue= */ "", - oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), - StatsLog.SETTING_CHANGED__REASON__DELETED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, + /* value= */ "", /* newValue= */ "", oldState.value, /* tag */ "", false, + getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED); addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState); changedKeys.add(key); // key was removed } @@ -516,9 +518,9 @@ final class SettingsState { continue; } - StatsLog.write(StatsLog.SETTING_CHANGED, key, value, state.value, oldValue, - /* tag */ null, /* make default */ false, - getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__UPDATED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, value, state.value, + oldValue, /* tag */ null, /* make default */ false, + getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED); addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state); } @@ -544,9 +546,9 @@ final class SettingsState { Setting oldState = mSettings.remove(name); - StatsLog.write(StatsLog.SETTING_CHANGED, name, /* value= */ "", /* newValue= */ "", - oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), - StatsLog.SETTING_CHANGED__REASON__DELETED); + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "", + /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), + FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED); updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null, oldState.defaultValue, null); @@ -1172,11 +1174,15 @@ final class SettingsState { public Setting(String name, String value, boolean makeDefault, String packageName, String tag) { + this(name, value, makeDefault, packageName, tag, false); + } + + Setting(String name, String value, boolean makeDefault, String packageName, + String tag, boolean forceNonSystemPackage) { this.name = name; // overrideableByRestore = true as the first initialization isn't considered a // modification. - update(value, makeDefault, packageName, tag, false, - /* overrideableByRestore */ true); + update(value, makeDefault, packageName, tag, forceNonSystemPackage, true); } public Setting(String name, String value, String defaultValue, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index e6508823c7e3..f5334fb7e6ea 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -32,6 +32,8 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.provider.settings.validators.SettingsValidators; +import android.provider.settings.validators.Validator; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; @@ -43,6 +45,7 @@ import org.junit.runner.RunWith; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -55,12 +58,24 @@ import java.util.concurrent.atomic.AtomicInteger; /** Tests for the SettingsHelperTest */ @RunWith(AndroidJUnit4.class) public class SettingsBackupAgentTest extends BaseSettingsProviderTest { - + private static final Uri TEST_URI = Uri.EMPTY; private static final String TEST_DISPLAY_DENSITY_FORCED = "123"; + private static final String OVERRIDDEN_TEST_SETTING = "overridden_setting"; + private static final String PRESERVED_TEST_SETTING = "preserved_setting"; + private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>(); private static final Map<String, String> TEST_VALUES = new HashMap<>(); + private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>(); static { - TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, TEST_DISPLAY_DENSITY_FORCED); + DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, + TEST_DISPLAY_DENSITY_FORCED); + + TEST_VALUES.put(OVERRIDDEN_TEST_SETTING, "123"); + TEST_VALUES.put(PRESERVED_TEST_SETTING, "124"); + + TEST_VALUES_VALIDATORS.put(OVERRIDDEN_TEST_SETTING, + SettingsValidators.ANY_STRING_VALIDATOR); + TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR); } private TestFriendlySettingsBackupAgent mAgentUnderTest; @@ -83,14 +98,15 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration(); - assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries); + assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries); mAgentUnderTest.restoreDeviceSpecificConfig( settingsBackup, R.array.restore_blocked_device_specific_settings, + Collections.emptySet(), Collections.emptySet()); - assertEquals("Not all values were restored.", TEST_VALUES, helper.mWrittenValues); + assertEquals("Not all values were restored.", DEVICE_SPECIFIC_TEST_VALUES, helper.mWrittenValues); } @Test @@ -100,12 +116,13 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration(); - assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries); - mAgentUnderTest.setBlockedSettings(TEST_VALUES.keySet().toArray(new String[0])); + assertEquals("Not all values backed up.", DEVICE_SPECIFIC_TEST_VALUES.keySet(), helper.mReadEntries); + mAgentUnderTest.setBlockedSettings(DEVICE_SPECIFIC_TEST_VALUES.keySet().toArray(new String[0])); mAgentUnderTest.restoreDeviceSpecificConfig( settingsBackup, R.array.restore_blocked_device_specific_settings, + Collections.emptySet(), Collections.emptySet()); assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty()); @@ -172,9 +189,50 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { mAgentUnderTest.restoreDeviceSpecificConfig( data, R.array.restore_blocked_device_specific_settings, + Collections.emptySet(), Collections.emptySet())); } + @Test + public void testOnRestore_preservedSettingsAreNotRestored() { + SettingsBackupAgent.SettingsBackupWhitelist whitelist = + new SettingsBackupAgent.SettingsBackupWhitelist( + new String[] { OVERRIDDEN_TEST_SETTING, PRESERVED_TEST_SETTING }, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsWhitelist(whitelist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, new HashSet<>(), + Collections.emptySet(), /* blockedSettingsArrayId */ 0, Collections.emptySet(), + new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI)))); + + assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING)); + assertFalse(settingsHelper.mWrittenValues.containsKey(PRESERVED_TEST_SETTING)); + } + + private byte[] generateBackupData(Map<String, String> keyValueData) { + int totalBytes = 0; + for (String key : keyValueData.keySet()) { + totalBytes += 2 * Integer.BYTES + key.getBytes().length + + keyValueData.get(key).getBytes().length; + } + + ByteBuffer buffer = ByteBuffer.allocate(totalBytes); + for (String key : keyValueData.keySet()) { + byte[] keyBytes = key.getBytes(); + byte[] valueBytes = keyValueData.get(key).getBytes(); + buffer.putInt(keyBytes.length); + buffer.put(keyBytes); + buffer.putInt(valueBytes.length); + buffer.put(valueBytes); + } + + return buffer.array(); + } + private byte[] generateUncorruptedHeader() throws IOException { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { mAgentUnderTest.writeHeader(os); @@ -219,6 +277,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent { private Boolean mForcedDeviceInfoRestoreAcceptability = null; private String[] mBlockedSettings = null; + private SettingsBackupWhitelist mSettingsWhitelist = null; void setForcedDeviceInfoRestoreAcceptability(boolean value) { mForcedDeviceInfoRestoreAcceptability = value; @@ -228,6 +287,10 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { mBlockedSettings = blockedSettings; } + void setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist) { + mSettingsWhitelist = settingsWhitelist; + } + @Override protected Set<String> getBlockedSettings(int blockedSettingsArrayId) { return mBlockedSettings == null @@ -241,6 +304,15 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { ? super.isSourceAcceptable(data, pos) : mForcedDeviceInfoRestoreAcceptability; } + + @Override + SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) { + if (mSettingsWhitelist == null) { + return super.getBackupWhitelist(contentUri); + } + + return mSettingsWhitelist; + } } /** The TestSettingsHelper tracks which values have been backed up and/or restored. */ @@ -257,7 +329,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { @Override public String onBackupValue(String key, String value) { mReadEntries.add(key); - String readValue = TEST_VALUES.get(key); + String readValue = DEVICE_SPECIFIC_TEST_VALUES.get(key); assert readValue != null; return readValue; } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index d67a9bc97cb8..8ff595b3bc53 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -33,7 +33,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; -import org.junit.Ignore; import org.junit.Test; import java.util.concurrent.atomic.AtomicBoolean; @@ -692,125 +691,4 @@ public class SettingsProviderTest extends BaseSettingsProviderTest { cursor.close(); } } - - @Test - @Ignore("b/140250974") - public void testLocationModeChanges_viaFrontEndApi() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_BATTERY_SAVING), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "network", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY), - UserHandle.USER_SYSTEM); - assertEquals( - "Wrong location providers", - "gps,network", - getStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - UserHandle.USER_SYSTEM)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowed_disableProviders() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY), - UserHandle.USER_SYSTEM); - - // Disable providers that were enabled - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-gps,-network"); - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - - // Disable a provider that was not enabled - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-test"); - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowed_enableAndDisable() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+gps,+network,+test"); - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test"); - - assertEquals( - "Wrong location providers", - "gps,network", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } - - @Test - @Ignore("b/140250974") - public void testLocationProvidersAllowedLocked_invalidInput() throws Exception { - setStringViaFrontEndApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_MODE, - String.valueOf(Settings.Secure.LOCATION_MODE_OFF), - UserHandle.USER_SYSTEM); - - // update providers with a invalid string - updateStringViaProviderApiSetting( - SETTING_TYPE_SECURE, - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+gps, invalid-string"); - - // Verifies providers list does not change - assertEquals( - "Wrong location providers", - "", - queryStringViaProviderApi( - SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)); - } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 0f3585303ed8..5946f2158ac8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -91,6 +91,7 @@ <uses-permission android:name="android.permission.TEST_MANAGE_ROLLBACKS" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> + <uses-permission android:name="android.permission.REBOOT" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.POWER_SAVER" /> <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" /> @@ -219,6 +220,14 @@ <!-- Permission required for CTS test - CrossProfileAppsHostSideTest --> <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/> + <!-- permissions required for CTS test - PhoneStateListenerTest --> + <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" /> + + <!-- Permissions required for ganting and logging --> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> + <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/> + <!-- Permission required for CTS test - UiModeManagerTest --> <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/> @@ -240,6 +249,9 @@ <!-- Allows setting brightness from the shell --> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/> + <!-- Permission required for CTS test - ShortcutManagerUsageTest --> + <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/> + <!-- Permissions required to test ambient display. --> <uses-permission android:name="android.permission.READ_DREAM_STATE"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> @@ -247,6 +259,9 @@ <!-- Permission required for CTS test - CtsLightsManagerTest --> <uses-permission android:name="android.permission.CONTROL_DEVICE_LIGHTS" /> + <!-- Permission needed to test mainline permission module rollback --> + <uses-permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" @@ -293,6 +308,22 @@ android:excludeFromRecents="true" android:exported="false" /> + <!-- + The following is used as a no-op/null home activity when + no other MAIN/HOME activity is present (e.g., in CSI). + --> + <activity android:name=".NullHome" + android:excludeFromRecents="true" + android:label="" + android:screenOrientation="nosensor"> + <!-- The priority here is set to be lower than that for Settings --> + <intent-filter android:priority="-1100"> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.HOME" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + <receiver android:name=".BugreportRequestedReceiver" android:permission="android.permission.TRIGGER_SHELL_BUGREPORT"> diff --git a/packages/Shell/res/layout/null_home_finishing_boot.xml b/packages/Shell/res/layout/null_home_finishing_boot.xml new file mode 100644 index 000000000000..5f9563a5d25c --- /dev/null +++ b/packages/Shell/res/layout/null_home_finishing_boot.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#80000000" + android:forceHasOverlappingRendering="false"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_gravity="center" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="40sp" + android:textColor="?android:attr/textColorPrimary" + android:text="@*android:string/android_start_title"/> + <ProgressBar + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12.75dp" + android:colorControlActivated="?android:attr/textColorPrimary" + android:indeterminate="true"/> + </LinearLayout> +</FrameLayout> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 48d405a4b91f..18145939c384 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -97,6 +97,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -193,9 +194,7 @@ public class BugreportProgressService extends Service { * <p> * Must be a path supported by its FileProvider. */ - // TODO: use the same variable for both dir - private static final String SCREENSHOT_DIR = "bugreports"; - private static final String BUGREPORT_DIR = "/bugreports"; + private static final String BUGREPORT_DIR = "bugreports"; private static final String NOTIFICATION_CHANNEL_ID = "bugreports"; @@ -230,7 +229,7 @@ public class BugreportProgressService extends Service { private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog(); - private File mScreenshotsDir; + private File mBugreportsDir; private BugreportManager mBugreportManager; @@ -263,11 +262,12 @@ public class BugreportProgressService extends Service { mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread"); startSelfIntent = new Intent(this, this.getClass()); - mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR); - if (!mScreenshotsDir.exists()) { - Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots"); - if (!mScreenshotsDir.mkdir()) { - Log.w(TAG, "Could not create directory " + mScreenshotsDir); + mBugreportsDir = new File(getFilesDir(), BUGREPORT_DIR); + if (!mBugreportsDir.exists()) { + Log.i(TAG, "Creating directory " + mBugreportsDir + + " to store bugreports and screenshots"); + if (!mBugreportsDir.mkdir()) { + Log.w(TAG, "Could not create directory " + mBugreportsDir); } } final Configuration conf = mContext.getResources().getConfiguration(); @@ -372,7 +372,7 @@ public class BugreportProgressService extends Service { @Override public void onFinished() { mInfo.renameBugreportFile(); - mInfo.renameScreenshots(mScreenshotsDir); + mInfo.renameScreenshots(); synchronized (mLock) { sendBugreportFinishedBroadcastLocked(); } @@ -406,7 +406,7 @@ public class BugreportProgressService extends Service { mInfo.bugreportFile); } else { trackInfoWithIdLocked(); - cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE); + cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir); final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED); intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath); intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo)); @@ -418,7 +418,8 @@ public class BugreportProgressService extends Service { private static void sendRemoteBugreportFinishedBroadcast(Context context, String bugreportFileName, File bugreportFile) { - cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE); + cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE, + bugreportFile.getParentFile()); final Intent intent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH); final Uri bugreportUri = getUri(context, bugreportFile); final String bugreportHash = generateFileHash(bugreportFileName); @@ -468,12 +469,12 @@ public class BugreportProgressService extends Service { return fileHash; } - static void cleanupOldFiles(final int minCount, final long minAge) { + static void cleanupOldFiles(final int minCount, final long minAge, File bugreportsDir) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { - FileUtils.deleteOlderFiles(new File(BUGREPORT_DIR), minCount, minAge); + FileUtils.deleteOlderFiles(bugreportsDir, minCount, minAge); } catch (RuntimeException e) { Log.e(TAG, "RuntimeException deleting old files", e); } @@ -604,18 +605,25 @@ public class BugreportProgressService extends Service { String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); BugreportInfo info = new BugreportInfo(mContext, baseName, name, - shareTitle, shareDescription, bugreportType); + shareTitle, shareDescription, bugreportType, mBugreportsDir); + ParcelFileDescriptor bugreportFd; + ParcelFileDescriptor screenshotFd; - ParcelFileDescriptor bugreportFd = info.createBugreportFd(); - if (bugreportFd == null) { - Log.e(TAG, "Bugreport parcel file descriptor is null."); - return; - } - ParcelFileDescriptor screenshotFd = info.createScreenshotFd(); - if (screenshotFd == null) { - Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file"); - FileUtils.closeQuietly(bugreportFd); - info.bugreportFile.delete(); + try { + bugreportFd = info.createAndGetBugreportFd(); + if (bugreportFd == null) { + Log.e(TAG, "Bugreport parcel file descriptor is null."); + return; + } + screenshotFd = info.createAndGetDefaultScreenshotFd(); + if (screenshotFd == null) { + Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file"); + FileUtils.closeQuietly(bugreportFd); + info.bugreportFile.delete(); + return; + } + } catch (IOException e) { + Log.e(TAG, "Error in generating bugreport files: ", e); return; } mBugreportManager = (BugreportManager) mContext.getSystemService( @@ -639,21 +647,24 @@ public class BugreportProgressService extends Service { } } - private static ParcelFileDescriptor createReadWriteFile(File file) { + private static ParcelFileDescriptor getFd(File file) { try { - file.createNewFile(); - file.setReadable(true, true); - file.setWritable(true, true); - - ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND); - return fd; - } catch (IOException e) { + } catch (FileNotFoundException e) { Log.i(TAG, "Error in generating bugreports: ", e); } return null; } + private static void createReadWriteFile(File file) throws IOException { + if (!file.exists()) { + file.createNewFile(); + file.setReadable(true, true); + file.setWritable(true, true); + } + } + /** * Updates the system notification for a given bugreport. */ @@ -874,7 +885,7 @@ public class BugreportProgressService extends Service { return; } final String screenshotPath = - new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath(); + new File(mBugreportsDir, info.getPathNextScreenshot()).getAbsolutePath(); Message.obtain(mScreenshotHandler, MSG_SCREENSHOT_REQUEST, id, UNUSED_ARG2, screenshotPath) .sendToTarget(); @@ -921,7 +932,7 @@ public class BugreportProgressService extends Service { info.addScreenshot(screenshotFile); if (info.finished) { Log.d(TAG, "Screenshot finished after bugreport; updating share notification"); - info.renameScreenshots(mScreenshotsDir); + info.renameScreenshots(); sendBugreportNotification(info, mTakingScreenshot); } msg = mContext.getString(R.string.bugreport_screenshot_taken); @@ -1030,11 +1041,10 @@ public class BugreportProgressService extends Service { /** * Build {@link Intent} that can be used to share the given bugreport. */ - private static Intent buildSendIntent(Context context, BugreportInfo info, - File screenshotsDir) { + private static Intent buildSendIntent(Context context, BugreportInfo info) { // Rename files (if required) before sharing info.renameBugreportFile(); - info.renameScreenshots(screenshotsDir); + info.renameScreenshots(); // Files are kept on private storage, so turn into Uris that we can // grant temporary permissions for. final Uri bugreportUri; @@ -1120,7 +1130,7 @@ public class BugreportProgressService extends Service { addDetailsToZipFile(info); - final Intent sendIntent = buildSendIntent(mContext, info, mScreenshotsDir); + final Intent sendIntent = buildSendIntent(mContext, info); if (sendIntent == null) { Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built"); synchronized (mLock) { @@ -1813,24 +1823,37 @@ public class BugreportProgressService extends Service { */ BugreportInfo(Context context, String baseName, String name, @Nullable String shareTitle, @Nullable String shareDescription, - @BugreportParams.BugreportMode int type) { + @BugreportParams.BugreportMode int type, File bugreportsDir) { this.context = context; this.name = this.initialName = name; this.shareTitle = shareTitle == null ? "" : shareTitle; this.shareDescription = shareDescription == null ? "" : shareDescription; this.type = type; this.baseName = baseName; + createBugreportFile(bugreportsDir); + createScreenshotFile(bugreportsDir); } - ParcelFileDescriptor createBugreportFd() { - bugreportFile = new File(BUGREPORT_DIR, getFileName(this, ".zip")); - return createReadWriteFile(bugreportFile); + void createBugreportFile(File bugreportsDir) { + bugreportFile = new File(bugreportsDir, getFileName(this, ".zip")); } - ParcelFileDescriptor createScreenshotFd() { - File screenshotFile = new File(BUGREPORT_DIR, getScreenshotName("default")); + void createScreenshotFile(File bugreportsDir) { + File screenshotFile = new File(bugreportsDir, getScreenshotName("default")); addScreenshot(screenshotFile); - return createReadWriteFile(screenshotFile); + } + + ParcelFileDescriptor createAndGetBugreportFd() throws IOException { + createReadWriteFile(bugreportFile); + return getFd(bugreportFile); + } + + ParcelFileDescriptor createAndGetDefaultScreenshotFd() throws IOException { + if (screenshotFiles.isEmpty()) { + return null; + } + createReadWriteFile(screenshotFiles.get(0)); + return getFd(screenshotFiles.get(0)); } /** @@ -1859,7 +1882,7 @@ public class BugreportProgressService extends Service { * Rename all screenshots files so that they contain the new {@code name} instead of the * {@code initialName} if user has changed it. */ - void renameScreenshots(File screenshotDir) { + void renameScreenshots() { if (TextUtils.isEmpty(name)) { return; } @@ -1869,7 +1892,7 @@ public class BugreportProgressService extends Service { final String newName = oldName.replaceFirst(initialName, name); final File newFile; if (!newName.equals(oldName)) { - final File renamedFile = new File(screenshotDir, newName); + final File renamedFile = new File(oldFile.getParentFile(), newName); Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile); newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile; } else { @@ -1889,7 +1912,8 @@ public class BugreportProgressService extends Service { * Rename bugreport file to include the name given by user via UI */ void renameBugreportFile() { - File newBugreportFile = new File(BUGREPORT_DIR, getFileName(this, ".zip")); + File newBugreportFile = new File(bugreportFile.getParentFile(), + getFileName(this, ".zip")); if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) { if (bugreportFile.renameTo(newBugreportFile)) { bugreportFile = newBugreportFile; diff --git a/packages/Shell/src/com/android/shell/NullHome.java b/packages/Shell/src/com/android/shell/NullHome.java new file mode 100644 index 000000000000..bd975614a50a --- /dev/null +++ b/packages/Shell/src/com/android/shell/NullHome.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.shell; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +/** + * This covers the fallback case where no launcher is available. + * Usually Settings.apk has one fallback home activity. + * Settings.apk, however, is not part of CSI, which needs to be + * standalone (bootable and testable). + */ +public class NullHome extends Activity { + private static final String TAG = "NullHome"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.i(TAG, "onCreate"); + setContentView(R.layout.null_home_finishing_boot); + } + + protected void onDestroy() { + super.onDestroy(); + Log.i(TAG, "onDestroy"); + } +} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 26fa1cf46974..5458676e1061 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -644,17 +644,19 @@ <activity android:name=".controls.management.ControlsProviderSelectorActivity" android:label="Controls Providers" - android:theme="@style/Theme.SystemUI" - android:exported="true" + android:theme="@style/Theme.ControlsManagement" + android:showForAllUsers="true" + android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> <activity android:name=".controls.management.ControlsFavoritingActivity" - android:parentActivityName=".controls.management.ControlsProviderSelectorActivity" - android:theme="@style/Theme.SystemUI" + android:theme="@style/Theme.ControlsManagement" android:excludeFromRecents="true" + android:showForAllUsers="true" + android:finishOnTaskLaunch="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/docs/QS-QQS.png b/packages/SystemUI/docs/QS-QQS.png Binary files differnew file mode 100644 index 000000000000..02de479cb8c0 --- /dev/null +++ b/packages/SystemUI/docs/QS-QQS.png diff --git a/packages/SystemUI/docs/plugin_hooks.md b/packages/SystemUI/docs/plugin_hooks.md index 2fb0c996111a..9fe2e181971a 100644 --- a/packages/SystemUI/docs/plugin_hooks.md +++ b/packages/SystemUI/docs/plugin_hooks.md @@ -56,11 +56,6 @@ Expected interface: [ClockPlugin](/packages/SystemUI/plugin/src/com/android/syst Use: Allows replacement of the keyguard main clock. -### Action: com.android.systemui.action.PLUGIN_NPV -Expected interface: [NPVPlugin](/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java) - -Use: Attach a view under QQS for prototyping. - # Global plugin dependencies These classes can be accessed by any plugin using PluginDependency as long as they @Requires them. diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md new file mode 100644 index 000000000000..b48ba6708313 --- /dev/null +++ b/packages/SystemUI/docs/qs-tiles.md @@ -0,0 +1,377 @@ +# Quick Settings Tiles (almost all there is to know about them) + +[TOC] + +## About this document + +This document is a more or less comprehensive summary of the state and infrastructure used by Quick Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and how SystemUI manages and displays tiles, among other topics. + +## What are Quick Settings Tiles? + +Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no text. + +Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages. + +The following image shows QQS on the left and QS on the right, with the tiles highlighted. + + + +QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. Controllers are obtained by the backend and used for communication between the user and the device. + +### A note on multi-user support + +All the classes described in this document that live inside SystemUI are only instantiated in the process of user 0. The different controllers that back the QS Tiles (also instantiated just in user 0) are user aware and provide an illusion of different instances for different users. + +For an example on this, see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). This controller for the `RotationLockTile` listens to changes in all users. + +## What are tiles made of? + +### Tile backend + +QS Tiles are composed of the following backend classes. + +* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface providing common behavior for all Tiles. This class also contains some useful utility classes needed for the tiles. + * `Icon`: Defines the basic interface for an icon as used by the tiles. + * `State`: Encapsulates the state of the Tile in order to communicate between the backend and the UI. +* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract implementation of `QSTile`, providing basic common behavior for all tiles. Also implements extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from this implementation. +* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These implementations connect to corresponding controllers. The controllers serve two purposes: + * track the state of the device and notify the tile when a change has occurred (for example, bluetooth connected to a device) + * accept actions from the tiles to modify the state of the phone (for example, enablind and disabling wifi). +* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information to be found in [`CustomTile`](#customtile) + +All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as examples of tiles. + +The interfaces in `QSTile` as well as other interfaces described in this document can be used to implement plugins to add additional tiles or different behavior. For more information, see [plugins.md](plugins.md) + +#### Tile State + +Each tile has an associated `State` object that is used to communicate information to the corresponding view. The base class `State` has (among others) the following fields: + +* **`state`**: one of `Tile#STATE_UNAVAILABLE`, `Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`. +* **`icon`**; icon to display. It may depend on the current state. +* **`label`**: usually the name of the tile. +* **`secondaryLabel`**: text to display in a second line. Usually extra state information. +* **`contentDescription`** +* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This will make screen readers read the current state of the tile as well as the new state when it's toggled. For this, the Tile has to use `BooleanState`. +* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set to `false` so it will not be announced for accessibility. + +Setting any of these fields during `QSTileImpl#handleUpdateState` will update the UI after it. + +Additionally. `BooleanState` has a `value` boolean field that usually would be set to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along with `expandedAccessibilityClassName`. + +#### SystemUI tiles + +Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common functions and leaves others to be implemented by each tile, in particular those that determine how to handle different events (refresh, click, etc.). + +For more information on how to implement a tile in SystemUI, see [Implementing a SystemUI tile](#implementing-a-systemui-tile). + +### Tile views + +Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`. + +* **[`com.android.systemui.plugins.qs.QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles. +* **[`QSTileBaseView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java)**: Implementation of `QSTileView` used in QQS that takes care of most of the features of the view: + * Holding the icon + * Background color and shape + * Ripple + * Click listening +* **[`QSTileView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java)**: Extends `QSTileBaseView`to add label support. Used in QS. +* **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)** +* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)** + +#### QSIconView and QSIconViewImpl + +`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles. + +This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the current `State` of the tile, modifying the icon (color and animations). Classes that inherit from this can add other details that are modified when the `State` changes. + +Each `QSTileImpl` can specify that they use a particular implementation of this class when creating an icon. + +### How are the backend and the views related? + +The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by using a `State`. The backend populates the state, and then the view maps the state to a visual representation. + +It's important to notice that the state of the tile (internal or visual) is not directly modified by a user action like clicking on the tile. Instead, acting on a tile produces internal state changes on the device, and those trigger the changes on the tile state and UI. + +When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the corresponding `QSTile` with its `QSTileView`, doing the following: + +* Create the corresponding `QSTileView` to display in that container. +* Create a callback for `QSTile` to call when its state changes. Note that a single tile will normally have up to two callbacks: one for QS and one for QQS. + +#### Life of a tile click + +This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`. + +1. User clicks on tile. The following calls happen in sequence: + 1. `QSTileBaseView#onClickListener`. + 2. `QSTile#click`. + 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller. +2. State in the device changes. This is normally outside of SystemUI's control. +3. Controller receives a callback (or `Intent`) indicating the change in the device. The following calls happen: + 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the new state. + 2. `QSTileImpl#handleRefreshState` +4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. +5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. +6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view: + * The tile is rippled and the color changes to match the new state. + * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view. + * If the tile is a `QSTileView` (in expanded QS), the labels are changed. + +## Third party tiles (TileService) + +A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This is implemented by developers subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and interacting with its API. + +### API classes + +The classes that define the public API are in [core/java/android/service/quicksettings](core/java/android/service/quicksettings). + +#### Tile + +Parcelable class used to communicate information about the state between the external app and SystemUI. The class supports the following fields: + +* Label +* Subtitle +* Icon +* State (`Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`, `Tile#STATE_UNAVAILABLE`) +* Content description + +Additionally, it provides a method to notify SystemUI that the information may have changed and the tile should be refreshed. + +#### TileService + +This is an abstract Service that needs to be implemented by the developer. The Service manifest must have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the available tiles and display them to the user. + +The implementer is responsible for creating the methods that will respond to the following calls from SystemUI: + +* **`onTileAdded`**: called when the tile is added to QS. +* **`onTileRemoved`**: called when the tile is removed from QS. +* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of the window when calling `getQSTile` is safe and will provide the correct object. +* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This marks the end of the window described in `onStartListening`. +* **`onClick`**: called when the user clicks on the tile. + +Additionally, the following final methods are provided: + +* ```java + public final Tile getQsTile() + ``` + + Provides the tile object that can be modified. This should only be called in the window between `onStartListening` and `onStopListening`. + +* ```java + public final boolean isLocked() + + public final boolean isSecure() + ``` + + Provide information about the secure state of the device. This can be used by the tile to accept or reject actions on the tile. + +* ```java + public final void unlockAndRun(Runnable) + ``` + + May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the given `Runnable`. + +* ```java + public final void showDialog(Dialog) + ``` + + Shows the provided dialog. + +##### Binding + +When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well as an identifier token (`Binder`). This token is used in the callbacks to identify this `TileService` and match it to the corresponding tile. + +The tiles are bound once immediately on creation. After that, the tile is bound whenever it should start listening. When the panels are closed, and the tile is set to stop listening, it will be unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening again. + +##### Active tile + +A `TileService` can be declared as an active tile by adding specific meta-data to its manifest (see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile must request listening status by making a call to `TileService#requestListeningState` with its component name. This will initiate a window that will last until the tile is updated. + +The tile will also be granted listening status if it's clicked by the user. + +### SystemUI classes + +The following sections describe the classes that live in SystemUI to support third party tiles. These classes live in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/) + +#### CustomTile + +This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and labels from the application manifest. + +#### TileServices + +This class is the central controller for all tile services that are currently in Quick Settings as well as provides the support for starting new ones. It is also an implementation of the `Binder` that receives all calls from current `TileService` components and dispatches them to SystemUI or the corresponding `CustomTile`. + +Whenever a binder call is made to this class, it matches the corresponding token assigned to the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to prevent spoofing. + +As this class is the only one that's aware of every `TileService` that's currently bound, it is also in charge of requesting some to be unbound whenever there is a low memory situation. + +#### TileLifecycleManager + +This class is in charge of binding and unbinding to a particular `TileService` when necessary, as well as sending the corresponding binder calls. It does not decide whether the tile should be bound or unbound, unless it's requested to process a message. It additionally handles errors in the `Binder` as well as changes in the corresponding component (like updates and enable/disable). + +The class has a queue that stores requests while the service is not bound, to be processed as soon as the service is bound. + +Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is added to the set of current ones and kept as long as the tile is available to the user. + +#### TileServiceManager + +Each instance of this class is an intermediary between the `TileServices` controller and a `TileLifecycleManager` corresponding to a particular `TileService`. + +This class handles management of the service, including: + +* Deciding when to bind and unbind, requesting it to the `TileLifecycleManager`. +* Relaying messages to the `TileService` through the `TileLifecycleManager`. +* Determining the service's bind priority (to deal with OOM situations). +* Detecting when the package/component has been removed in order to remove the tile and references to it. + +## How are tiles created/instantiated? + +This section describes the classes that aid in the creation of each tile as well as the complete lifecycle of a tile. First we describe two important interfaces/classes. + +### QSTileHost + +This class keeps track of the tiles selected by the current user (backed in the Secure Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting changes (or on device start), the whole list of tiles is read. This is compared with the current tiles, destroying unnecessary ones and creating needed ones. + +It additionally provides a point of communication between the tiles and the StatusBar, for example to open it and collapse it. And a way for the StatusBar service to add tiles (only works for `CustomTile`). + +#### Tile specs + +Each single tile is identified by a spec, which is a unique String for that type of tile. The current tiles are stored as a Setting string of comma separated values of these specs. Additionally, the default tiles (that appear on a fresh system) configuration value is stored likewise. + +SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is a flattened String representing the `ComponentName` for the corresponding `TileService`. + +### QSFactory + +This interface provides a way of creating tiles and views from a spec. It can be used in plugins to provide different definitions for tiles. + +In SystemUI there is only one implementation of this factory and that is the default factory (`QSFactoryImpl`) in `QSTileHost`. + +#### QSFactoryImpl + +This class implements two methods as specified in the `QSFactory` interface: + +* ```java + public QSTile createTile(String) + ``` + + Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI tiles, returning one when the correct spec is used. + + If the spec is not recognized but it has the `custom(` prefix, the factory tries to create a `CustomTile` for the component in the spec. This could fail (the component is not a valid `TileService` or is not enabled) and will be detected later when the tile is polled to determine if it's available. + +* ```java + public QSTileView createTileView(QSTile, boolean) + ``` + + Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is created should be a collapsed one (for using in QQS) or not (for using in QS). + +### Lifecycle of a Tile + +We describe first the parts of the lifecycle that are common to SystemUI tiles and third party tiles. Following that, there will be a section with the steps that are exclusive to third party tiles. + +1. The tile is added through the QS customizer by the user. This will immediately save the new list of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`). +2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new value of the setting and finds out that there is a new spec in the list. Alternatively, when the device is booted, all tiles in the setting are considered as "new". +3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things proceed forward. +4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two classes. +5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to a `TileRecord` containing the tile backend and the view. Additionally: + * a callback is attached to the tile to communicate between the backend and the view or the panel. + * the click listeners in the tile are attached to those of the view. +6. The tile view is added to the corresponding layout. + +When the tile is removed from the list of current tiles, all these classes are properly disposed including removing the callbacks and making sure that the backends remove themselves from the controllers they were listening to. + +#### Lifecycle of a CustomTile + +In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to ensure the proper binding to the service as described in [Third party tiles (TileService)](#third-party-tiles-tileservice). + +1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains the `ComponentName` of the associated service, this can be used to bind to it. +2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the service. +3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the token and the `ComponentName`. + +## Implementing a tile + +This section describes necessary and recommended steps when implementing a Quick Settings tile. Some of them are optional and depend on the requirements of the tile. + +### Implementing a SystemUI tile + +1. Create a class (preferably in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) implementing `QSTileImpl` with a particular type of `State` as a parameter. +2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the tile's operation. Normally this would be other SystemUI controllers. +3. Implement the methods described in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for help. Some considerations to have in mind: + * If the tile will not support long click (like the `FlashlightTile`), set `state.handlesLongClick` to `false` (maybe in `newTileState`). + * Changes to the tile state (either from controllers or from clicks) should call `refreshState`. + * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter. + * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers. + * Implement `isAvailable` so the tile will not be created when it's not necessary. +4. In `QSFactoryImpl`: + * Inject a `Provider` for the tile created before. + * Add a case to the `switch` with a unique String spec for the chosen tile. +5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. + +#### Abstract methods in QSTileImpl + +Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a type variable of type `State`. + +* ```java + public TState newTileState() + ``` + + Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has an on and off state and provides this as a content description), `SignalState` (`BooleanState` with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through). + + If a tile has special behavior (no long click, no ripple), it can be set in its state here. + +* ```java + public void handleSetListening(boolean) + ``` + + Initiates or terminates listening behavior, like listening to Callbacks from controllers. This gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the lifecycle of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java) + +* ```java + public QSIconView createTileView(Context) + ``` + + Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` (see [Tile views](#tile-views)), which is the default defined in `QSTileImpl` + +* ```java + public Intent getLongClickIntent() + ``` + + Determines the `Intent` launched when the Tile is long pressed. + +* ```java + protected void handleClick() + + protected void handleSecondaryClick() + + protected void handleLongClick() + ``` + + Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`. + + By default long click redirects to click and long click launches the intent defined in `getLongClickIntent`. + +* ```java + protected void handleUpdateState(TState, Object) + ``` + + Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI. + +* ```java + public int getMetricsCategory() + ``` + + Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile. + +* ```java + public boolean isAvailable() + ``` + + Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no Wifi support). If this is false, the Tile will be destroyed upon creation. + +* ```java + public CharSequence getTileLabel() + ``` + + Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to each available tile. + +### Implementing a third party tile + +For information about this, use the Android Developer documentation for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java deleted file mode 100644 index c1d4b03b6620..000000000000 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java +++ /dev/null @@ -1,41 +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.plugins; - -import android.view.ViewGroup; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Test plugin for home controls - */ -@ProvidesInterface(action = HomeControlsPlugin.ACTION, version = HomeControlsPlugin.VERSION) -public interface HomeControlsPlugin extends Plugin { - - String ACTION = "com.android.systemui.action.PLUGIN_HOME_CONTROLS"; - int VERSION = 1; - - /** - * Pass the container for the plugin to use however it wants. Ideally the plugin impl - * will add home controls to this space. - */ - void sendParentGroup(ViewGroup group); - - /** - * When visible, will poll for updates. - */ - void setVisible(boolean visible); -} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java deleted file mode 100644 index 1426266a7048..000000000000 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java +++ /dev/null @@ -1,52 +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.plugins; - -import android.view.View; -import android.widget.FrameLayout; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Plugin to attach custom views under QQS. - * - * A parent view is provided to the plugin to which they can add Views. - * <br> - * The parent is a {@link FrameLayout} with same background as QS and 96dp height. - * - * {@see NPVPluginManager} - * {@see status_bar_expanded_plugin_frame} - */ -@ProvidesInterface(action = NPVPlugin.ACTION, version = NPVPlugin.VERSION) -public interface NPVPlugin extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_NPV"; - int VERSION = 1; - - /** - * Attach views to the parent. - * - * @param parent a {@link FrameLayout} to which to attach views. Preferably a root view. - * @return a view attached to parent. - */ - View attachToRoot(FrameLayout parent); - - /** - * Indicate to the plugin when it is listening (QS expanded) - * @param listening - */ - default void setListening(boolean listening) {}; -} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 17f2f476c9f2..01811e9cdced 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -14,9 +14,6 @@ package com.android.systemui.plugins.qs; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.annotation.IntDef; import android.content.Context; import android.graphics.drawable.Drawable; import android.metrics.LogMaker; @@ -28,7 +25,6 @@ import com.android.systemui.plugins.qs.QSTile.Callback; import com.android.systemui.plugins.qs.QSTile.Icon; import com.android.systemui.plugins.qs.QSTile.State; -import java.lang.annotation.Retention; import java.util.Objects; import java.util.function.Supplier; @@ -84,17 +80,6 @@ public interface QSTile { return logMaker; } - @Retention(SOURCE) - @IntDef({COLOR_TILE_ACCENT, COLOR_TILE_RED, COLOR_TILE_BLUE, COLOR_TILE_YELLOW, - COLOR_TILE_GREEN}) - @interface ColorTile {} - int COLOR_TILE_ACCENT = 0; - int COLOR_TILE_RED = 1; - int COLOR_TILE_BLUE = 2; - int COLOR_TILE_YELLOW = 3; - int COLOR_TILE_GREEN = 4; - default void setColor(@ColorTile int color) {} - @ProvidesInterface(version = Callback.VERSION) public interface Callback { public static final int VERSION = 1; @@ -133,7 +118,6 @@ public interface QSTile { public CharSequence label; public CharSequence secondaryLabel; public CharSequence contentDescription; - public CharSequence stateDescription; public CharSequence dualLabelContentDescription; public boolean disabledByPolicy; public boolean dualTarget = false; @@ -142,7 +126,6 @@ public interface QSTile { public SlashState slash; public boolean handlesLongClick = true; public boolean showRippleEffect = true; - public int colorActive = -1; public boolean copyTo(State other) { if (other == null) throw new IllegalArgumentException(); @@ -152,7 +135,6 @@ public interface QSTile { || !Objects.equals(other.label, label) || !Objects.equals(other.secondaryLabel, secondaryLabel) || !Objects.equals(other.contentDescription, contentDescription) - || !Objects.equals(other.stateDescription, stateDescription) || !Objects.equals(other.dualLabelContentDescription, dualLabelContentDescription) || !Objects.equals(other.expandedAccessibilityClassName, @@ -163,14 +145,12 @@ public interface QSTile { || !Objects.equals(other.dualTarget, dualTarget) || !Objects.equals(other.slash, slash) || !Objects.equals(other.handlesLongClick, handlesLongClick) - || !Objects.equals(other.showRippleEffect, showRippleEffect) - || !Objects.equals(other.colorActive, colorActive); + || !Objects.equals(other.showRippleEffect, showRippleEffect); other.icon = icon; other.iconSupplier = iconSupplier; other.label = label; other.secondaryLabel = secondaryLabel; other.contentDescription = contentDescription; - other.stateDescription = stateDescription; other.dualLabelContentDescription = dualLabelContentDescription; other.expandedAccessibilityClassName = expandedAccessibilityClassName; other.disabledByPolicy = disabledByPolicy; @@ -180,7 +160,6 @@ public interface QSTile { other.slash = slash != null ? slash.copy() : null; other.handlesLongClick = handlesLongClick; other.showRippleEffect = showRippleEffect; - other.colorActive = colorActive; return changed; } @@ -198,7 +177,6 @@ public interface QSTile { sb.append(",label=").append(label); sb.append(",secondaryLabel=").append(secondaryLabel); sb.append(",contentDescription=").append(contentDescription); - sb.append(",stateDescription=").append(stateDescription); sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); sb.append(",disabledByPolicy=").append(disabledByPolicy); diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 3e74970ee725..2a2ba1b0ccaa 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -16,6 +16,7 @@ -keep class com.android.systemui.statusbar.tv.TvStatusBar -keep class com.android.systemui.car.CarSystemUIFactory -keep class com.android.systemui.SystemUIFactory +-keep class com.android.systemui.tv.TvSystemUIFactory -keep class * extends com.android.systemui.SystemUI -keep class * implements com.android.systemui.SystemUI$Injector diff --git a/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml new file mode 100644 index 000000000000..7b43a0315c10 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + android:layout_marginStart="12dp" + android:layout_marginEnd="2dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp"> + +</TextView>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index bde6ed531353..8d9d6ee68c67 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -22,10 +22,4 @@ <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] --> <bool name="config_disableMenuKeyInLockScreen">false</bool> - - <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V --> - <integer name="config_chargingSlowlyThreshold">5000000</integer> - - <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V --> - <integer name="config_chargingFastThreshold">7500000</integer> </resources> diff --git a/packages/SystemUI/res/color/lock_background.xml b/packages/SystemUI/res/color/control_background.xml index 646fe5dfe712..646fe5dfe712 100644 --- a/packages/SystemUI/res/color/lock_background.xml +++ b/packages/SystemUI/res/color/control_background.xml diff --git a/packages/SystemUI/res/color/unknown_foreground.xml b/packages/SystemUI/res/color/control_foreground.xml index bf028f18a7de..bf028f18a7de 100644 --- a/packages/SystemUI/res/color/unknown_foreground.xml +++ b/packages/SystemUI/res/color/control_foreground.xml diff --git a/packages/SystemUI/res/color/thermo_cool_background.xml b/packages/SystemUI/res/color/thermo_cool_background.xml new file mode 100644 index 000000000000..646fe5dfe712 --- /dev/null +++ b/packages/SystemUI/res/color/thermo_cool_background.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:color="@color/control_default_background" /> + <item android:color="@color/GM2_blue_50" /> +</selector> diff --git a/packages/SystemUI/res/color/thermo_cool_foreground.xml b/packages/SystemUI/res/color/thermo_cool_foreground.xml new file mode 100644 index 000000000000..bf028f18a7de --- /dev/null +++ b/packages/SystemUI/res/color/thermo_cool_foreground.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:color="@color/control_default_foreground" /> + <item android:color="@color/GM2_blue_700" /> + </selector> diff --git a/packages/SystemUI/res/color/thermo_heat_background.xml b/packages/SystemUI/res/color/thermo_heat_background.xml new file mode 100644 index 000000000000..6f29ed5f60ac --- /dev/null +++ b/packages/SystemUI/res/color/thermo_heat_background.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:color="@color/control_default_background" /> + <item android:color="@color/GM2_red_50" /> +</selector> diff --git a/packages/SystemUI/res/color/lock_foreground.xml b/packages/SystemUI/res/color/thermo_heat_foreground.xml index 3e05653bce92..72f4b8d13458 100644 --- a/packages/SystemUI/res/color/lock_foreground.xml +++ b/packages/SystemUI/res/color/thermo_heat_foreground.xml @@ -2,5 +2,5 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false" android:color="@color/control_default_foreground" /> - <item android:color="@color/GM2_blue_700" /> + <item android:color="@color/GM2_red_700" /> </selector> diff --git a/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml new file mode 100644 index 000000000000..73fd37f1bdd6 --- /dev/null +++ b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml @@ -0,0 +1,63 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:pathData="M77.773,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z" + android:fillColor="#F86734"/> + <path + android:pathData="M83.598,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z" + android:fillColor="#F86734"/> + <path + android:pathData="M70.044,75.974m-0.644,0a0.644,0.644 0,1 1,1.288 0a0.644,0.644 0,1 1,-1.288 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M56.896,80.985m-0.718,0a0.718,0.718 0,1 1,1.436 0a0.718,0.718 0,1 1,-1.436 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M43.408,78.881m-0.795,0a0.795,0.795 0,1 1,1.59 0a0.795,0.795 0,1 1,-1.59 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M32.419,70.115m-0.874,0a0.874,0.874 0,1 1,1.748 0a0.874,0.874 0,1 1,-1.748 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M27.306,56.992m-0.954,0a0.954,0.954 0,1 1,1.908 0a0.954,0.954 0,1 1,-1.908 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M29.313,43.489m-1.036,0a1.036,1.036 0,1 1,2.072 0a1.036,1.036 0,1 1,-2.072 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M37.988,32.445m-1.118,0a1.118,1.118 0,1 1,2.236 0a1.118,1.118 0,1 1,-2.236 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M51.137,27.064m-1.201,0a1.201,1.201 0,1 1,2.402 0a1.201,1.201 0,1 1,-2.402 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M64.553,28.868m-1.284,0a1.284,1.284 0,1 1,2.568 0a1.284,1.284 0,1 1,-2.568 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M75.522,37.652m-1.368,0a1.368,1.368 0,1 1,2.736 0a1.368,1.368 0,1 1,-2.736 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M87.942,115.052l-47.557,-47.557l26.869,-26.87l47.557,47.558z"> + <aapt:attr name="android:fillColor"> + <gradient + android:startY="56.087" + android:startX="55.8464" + android:endY="100.0297" + android:endX="99.7891" + android:type="linear"> + <item android:offset="0" android:color="#3F000000"/> + <item android:offset="1" android:color="#00000000"/> + </gradient> + </aapt:attr> + </path> + <path + android:pathData="M53.928,54.17m-18.999,0a18.999,18.999 0,1 1,37.998 0a18.999,18.999 0,1 1,-37.998 0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M66.353,54.17m-3.185,0a3.185,3.185 0,1 1,6.37 0a3.185,3.185 0,1 1,-6.37 0" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml index 7a68c032d8be..7f8d4fa8833f 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon.xml @@ -15,5 +15,5 @@ Copyright (C) 2018 The Android Open Source Project --> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/icon_bg"/> - <foreground android:drawable="@drawable/q"/> + <foreground android:drawable="@drawable/android_11_dial"/> </adaptive-icon> diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml index 2a54dfad8191..31b2a7f9a333 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml @@ -14,5 +14,5 @@ Copyright (C) 2018 The Android Open Source Project limitations under the License. --> <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#77C360" /> + android:color="#073042" /> diff --git a/packages/SystemUI/res/drawable-nodpi/q.xml b/packages/SystemUI/res/drawable-nodpi/q.xml deleted file mode 100644 index 0f42d2e20040..000000000000 --- a/packages/SystemUI/res/drawable-nodpi/q.xml +++ /dev/null @@ -1,40 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108.0" - android:viewportHeight="108.0"> - <group - android:name="scale" - android:pivotX="54" android:pivotY="54" - android:scaleX="0.9" - android:scaleY="0.9"> - <group - android:name="nudge" - android:translateX="24" - android:translateY="23.5"> - <path - android:name="tail" - android:fillColor="#FFFFFF" - android:pathData="M21.749674,34.122784l-9.431964,9.529709l-6.31771,-6.2529106l15.736504,-15.899582l64.765724,65.16436l-6.3046494,6.266083z"/> - <path - android:name="counter" - android:fillColor="#FFFFFF" - android:pathData="M30,9.32352941 C41.6954418,9.32352941 51.1764706,18.8045582 51.1764706,30.5 C51.1764706,42.1954418 41.6954418,51.6764706 30,51.6764706 C18.3045582,51.6764706 8.82352941,42.1954418 8.82352941,30.5 C8.82352941,18.8045582 18.3045582,9.32352941 30,9.32352941 L30,9.32352941 Z M30,0.5 C13.4314575,0.5 -5.53805368e-15,13.9314575 -7.10542736e-15,30.5 C-1.02401747e-14,47.0685425 13.4314575,60.5 30,60.5 C46.5685425,60.5 60,47.0685425 60,30.5 C59.9805514,13.9395201 46.5604799,0.519448617 30,0.5 Z"/> - </group> - </group> -</vector> diff --git a/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml new file mode 100644 index 000000000000..c547c52a4077 --- /dev/null +++ b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM14,6h-4L10,4h4v2z" + android:fillColor="?android:attr/colorAccent"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml index 1e282ad0eec7..947c77b4e39e 100644 --- a/packages/SystemUI/res/drawable/control_no_favorites_background.xml +++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml @@ -17,6 +17,6 @@ */ --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <stroke android:width="1dp" android:color="?android:attr/colorBackgroundFloating"/> + <stroke android:width="1dp" android:color="@*android:color/foreground_material_dark"/> <corners android:radius="@dimen/control_corner_radius" /> </shape> diff --git a/packages/SystemUI/res/drawable/ic_cancel_24.xml b/packages/SystemUI/res/drawable/ic_cancel_24.xml new file mode 100644 index 000000000000..8ab28ddb680d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_cancel_24.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml new file mode 100644 index 000000000000..24e063506250 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2l-5.5,9h11L12,2zM12,5.84L13.93,9h-3.87L12,5.84zM17.5,13c-2.49,0 -4.5,2.01 -4.5,4.5s2.01,4.5 4.5,4.5 4.5,-2.01 4.5,-4.5 -2.01,-4.5 -4.5,-4.5zM17.5,20c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM3,21.5h8v-8L3,13.5v8zM5,15.5h4v4L5,19.5v-4z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_important.xml b/packages/SystemUI/res/drawable/ic_important.xml new file mode 100644 index 000000000000..d7439e167dd4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M4,18.99h11c0.67,0 1.27,-0.32 1.63,-0.83L21,12l-4.37,-6.16C16.27,5.33 15.67,5 15,5H4l5,7 -5,6.99z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml new file mode 100644 index 000000000000..7a628bb65433 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important_outline.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M15,19L3,19l4.5,-7L3,5h12c0.65,0 1.26,0.31 1.63,0.84L21,12l-4.37,6.16c-0.37,0.52 -0.98,0.84 -1.63,0.84zM6.5,17L15,17l3.5,-5L15,7L6.5,7l3.5,5 -3.5,5z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml deleted file mode 100644 index 4a731b35b423..000000000000 --- a/packages/SystemUI/res/drawable/ic_star.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml deleted file mode 100644 index 9ede40be3b7b..000000000000 --- a/packages/SystemUI/res/drawable/ic_star_border.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_touch.xml b/packages/SystemUI/res/drawable/ic_touch.xml new file mode 100644 index 000000000000..4f6698de5251 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_touch.xml @@ -0,0 +1,26 @@ +<!-- +Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<!-- maybe need android:fillType="evenOdd" --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M9,7.5V11.24C7.79,10.43 7,9.06 7,7.5C7,5.01 9.01,3 11.5,3C13.99,3 16,5.01 16,7.5C16,9.06 15.21,10.43 14,11.24V7.5C14,6.12 12.88,5 11.5,5C10.12,5 9,6.12 9,7.5ZM14.3,13.61L18.84,15.87C19.37,16.09 19.75,16.63 19.75,17.25C19.75,17.31 19.74,17.38 19.73,17.45L18.98,22.72C18.87,23.45 18.29,24 17.54,24H10.75C10.34,24 9.96,23.83 9.69,23.56L4.75,18.62L5.54,17.82C5.74,17.62 6.02,17.49 6.33,17.49C6.39,17.49 6.4411,17.4989 6.4922,17.5078C6.5178,17.5122 6.5433,17.5167 6.57,17.52L10,18.24V7.5C10,6.67 10.67,6 11.5,6C12.33,6 13,6.67 13,7.5V13.5H13.76C13.95,13.5 14.13,13.54 14.3,13.61Z" + /> +</vector> diff --git a/packages/SystemUI/res/drawable/notif_dungeon_bg_gradient.xml b/packages/SystemUI/res/drawable/notif_dungeon_bg_gradient.xml new file mode 100644 index 000000000000..e456e2965d21 --- /dev/null +++ b/packages/SystemUI/res/drawable/notif_dungeon_bg_gradient.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="90" + android:startColor="#ff000000" + android:endColor="#00000000" + android:type="linear" /> +</shape> diff --git a/packages/SystemUI/res/drawable/qs_media_background.xml b/packages/SystemUI/res/drawable/qs_media_background.xml new file mode 100644 index 000000000000..2821e4c28bab --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_media_background.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners + android:bottomLeftRadius="@dimen/qs_media_corner_radius" + android:topLeftRadius="@dimen/qs_media_corner_radius" + android:bottomRightRadius="@dimen/qs_media_corner_radius" + android:topRightRadius="@dimen/qs_media_corner_radius" + /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml new file mode 100644 index 000000000000..be3c5983bb2e --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0" + android:fillColor="@android:color/white"/> + <path + android:fillColor="@color/GM2_grey_500" + android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml index c3fa39e5a87f..c40e47df9a55 100644 --- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml +++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml @@ -34,20 +34,15 @@ android:layout_weight="1"/> <ImageView - android:layout_width="32dp" - android:layout_height="32dp" - android:background="@drawable/auth_dialog_lock"/> + android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> <TextView android:id="@+id/title" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:layout_marginTop="12dp" - android:textSize="20sp" - android:gravity="center" - android:textColor="?android:attr/textColorPrimary"/> + style="@style/TextAppearance.AuthCredential.Title"/> <TextView android:id="@+id/subtitle" @@ -63,17 +58,41 @@ android:id="@+id/description" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:layout_marginTop="8dp" - android:gravity="center" - android:textSize="16sp" - android:textColor="?android:attr/textColorPrimary"/> + style="@style/TextAppearance.AuthCredential.Description"/> <Space android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center" + android:paddingLeft="0dp" + android:paddingRight="0dp" + android:paddingTop="0dp" + android:paddingBottom="16dp" + android:clipToPadding="false"> + + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1" + style="@style/LockPatternContainerStyle"> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPattern" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + style="@style/LockPatternStyleBiometricPrompt"/> + + </FrameLayout> + <TextView android:id="@+id/error" android:layout_width="match_parent" @@ -90,24 +109,4 @@ </LinearLayout> - <LinearLayout - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="vertical" - android:gravity="center"> - - <com.android.internal.widget.LockPatternView - android:id="@+id/lockPattern" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="40dp" - android:layout_marginRight="40dp" - android:layout_gravity="center" - android:clipChildren="false" - android:clipToPadding="false" - style="@style/LockPatternStyleBiometricPrompt"/> - - </LinearLayout> - </com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml deleted file mode 100644 index 83e788731442..000000000000 --- a/packages/SystemUI/res/layout/app_item.xml +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:attr/selectableItemBackground" - android:gravity="center_vertical" - android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> - - <LinearLayout - android:id="@+id/icon_frame" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="start|center_vertical" - android:minWidth="56dp" - android:orientation="horizontal" - android:paddingEnd="8dp" - android:paddingTop="4dp" - android:paddingBottom="4dp"> - <ImageView - android:id="@android:id/icon" - android:layout_width="@dimen/app_icon_size" - android:layout_height="@dimen/app_icon_size"/> - </LinearLayout> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical" - android:paddingTop="16dp" - android:paddingBottom="16dp"> - - <TextView - android:id="@android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceListItem"/> - - </LinearLayout> - - <LinearLayout - android:id="@android:id/widget_frame" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical|end" - android:minWidth="64dp" - android:orientation="vertical"/> - -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml index 4aed0333e9ca..b14bc7de06c4 100644 --- a/packages/SystemUI/res/layout/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -28,20 +28,15 @@ android:layout_weight="1"/> <ImageView - android:layout_width="32dp" - android:layout_height="32dp" - android:background="@drawable/auth_dialog_lock"/> + android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> <TextView android:id="@+id/title" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:layout_marginTop="12dp" - android:textSize="20sp" - android:gravity="center" - android:textColor="?android:attr/textColorPrimary"/> + style="@style/TextAppearance.AuthCredential.Title"/> <TextView android:id="@+id/subtitle" @@ -57,11 +52,7 @@ android:id="@+id/description" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:layout_marginTop="8dp" - android:gravity="center" - android:textSize="16sp" - android:textColor="?android:attr/textColorPrimary"/> + style="@style/TextAppearance.AuthCredential.Description"/> <Space android:layout_width="0dp" @@ -79,19 +70,15 @@ <EditText android:id="@+id/lockPassword" - android:layout_marginBottom="20dp" - android:layout_marginLeft="100dp" - android:layout_marginRight="100dp" android:layout_width="208dp" android:layout_height="wrap_content" - android:layout_gravity="center" + android:layout_gravity="center_horizontal" + android:minHeight="48dp" android:gravity="center" android:inputType="textPassword" android:maxLength="500" - android:textSize="16sp" - android:textAppearance="?android:attr/textAppearanceMedium" - android:imeOptions="flagForceAscii" - style="@style/LockPatternStyleBiometricPrompt"/> + android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" + style="@style/TextAppearance.AuthCredential.PasswordEntry"/> <Space android:layout_width="0dp" diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml index c9edcd606277..eda5ecbf7f8c 100644 --- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml @@ -28,20 +28,15 @@ android:layout_weight="1"/> <ImageView - android:layout_width="32dp" - android:layout_height="32dp" - android:background="@drawable/auth_dialog_lock"/> + android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> <TextView android:id="@+id/title" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:layout_marginTop="12dp" - android:textSize="20sp" - android:gravity="center" - android:textColor="?android:attr/textColorPrimary"/> + style="@style/TextAppearance.AuthCredential.Title"/> <TextView android:id="@+id/subtitle" @@ -57,37 +52,49 @@ android:id="@+id/description" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:layout_marginTop="8dp" - android:gravity="center" - android:textSize="16sp" - android:textColor="?android:attr/textColorPrimary"/> + style="@style/TextAppearance.AuthCredential.Description"/> <Space android:layout_width="0dp" android:layout_height="0dp" - android:layout_weight="3"/> + android:layout_weight="1"/> - <TextView - android:id="@+id/error" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" - android:textSize="16sp" + android:orientation="vertical" android:gravity="center" - android:textColor="?android:attr/colorError"/> + android:paddingLeft="0dp" + android:paddingRight="0dp" + android:paddingTop="0dp" + android:paddingBottom="16dp" + android:clipToPadding="false"> - <com.android.internal.widget.LockPatternView - android:id="@+id/lockPattern" - android:layout_marginBottom="20dp" - android:layout_marginLeft="40dp" - android:layout_marginRight="40dp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:clipChildren="false" - android:clipToPadding="false" - style="@style/LockPatternStyleBiometricPrompt"/> + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1" + style="@style/LockPatternContainerStyle"> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPattern" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + style="@style/LockPatternStyleBiometricPrompt"/> + + </FrameLayout> + + <TextView + android:id="@+id/error" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="24dp" + android:textSize="16sp" + android:gravity="center" + android:textColor="?android:attr/colorError"/> + + </LinearLayout> <Space android:layout_width="0dp" diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml deleted file mode 100644 index 85701aaca41d..000000000000 --- a/packages/SystemUI/res/layout/control_item.xml +++ /dev/null @@ -1,72 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<androidx.constraintlayout.widget.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="100dp" - android:padding="15dp" - android:clickable="true" - android:focusable="true"> - - <ImageView - android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <TextView - android:id="@+id/status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="12sp" - android:textColor="?android:attr/textColorPrimary" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:paddingLeft="3dp" - app:layout_constraintBottom_toBottomOf="@+id/icon" - app:layout_constraintStart_toEndOf="@+id/icon" /> - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="18sp" - android:textColor="?android:attr/textColorPrimary" - android:fontFamily="@*android:string/config_headlineFontFamily" - app:layout_constraintBottom_toTopOf="@+id/subtitle" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/icon" /> - - <TextView - android:id="@+id/subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="16sp" - android:textColor="?android:attr/textColorSecondary" - android:fontFamily="@*android:string/config_headlineFontFamily" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" /> - - <CheckBox - android:id="@+id/favorite" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent"/> -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/controls_app_item.xml b/packages/SystemUI/res/layout/controls_app_item.xml new file mode 100644 index 000000000000..d54cd6db867a --- /dev/null +++ b/packages/SystemUI/res/layout/controls_app_item.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/selectableItemBackground"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="start|top" + android:gravity="center_vertical" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:layout_marginBottom="@dimen/controls_app_bottom_margin"> + + <FrameLayout + android:id="@+id/icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="start|center_vertical" + android:minWidth="56dp" + android:orientation="horizontal" + android:paddingTop="@dimen/controls_app_icon_frame_top_padding" + android:paddingBottom="@dimen/controls_app_icon_frame_top_padding" + android:paddingEnd="@dimen/controls_app_icon_frame_side_padding" + android:paddingStart="@dimen/controls_app_icon_frame_side_padding" > + + <ImageView + android:id="@android:id/icon" + android:layout_width="@dimen/controls_app_icon_size" + android:layout_height="@dimen/controls_app_icon_size" /> + </FrameLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:paddingTop="@dimen/controls_app_text_padding" + android:paddingBottom="@dimen/controls_app_text_padding"> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/textColorPrimary"/> + + <TextView + android:id="@+id/favorites" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + </LinearLayout> + + </LinearLayout> + <View + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginStart="@dimen/controls_app_divider_side_margin" + android:layout_marginEnd="@dimen/controls_app_divider_side_margin" + android:background="?android:attr/listDivider" /> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 3c4c61e30bc1..823bbcd6e68c 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -23,8 +23,8 @@ android:padding="@dimen/control_padding" android:clickable="true" android:focusable="true" - android:layout_marginLeft="3dp" - android:layout_marginRight="3dp" + android:layout_marginLeft="@dimen/control_base_item_margin" + android:layout_marginRight="@dimen/control_base_item_margin" android:background="@drawable/control_background"> <ImageView @@ -38,10 +38,8 @@ android:id="@+id/status" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="@dimen/control_status_normal" - android:textColor="?android:attr/textColorPrimary" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:paddingLeft="3dp" + android:textAppearance="@style/TextAppearance.Control.Status" + android:paddingStart="@dimen/control_status_padding" app:layout_constraintBottom_toBottomOf="@+id/icon" app:layout_constraintStart_toEndOf="@+id/icon" /> @@ -49,10 +47,8 @@ android:id="@+id/status_extra" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="@dimen/control_status_normal" - android:textColor="?android:attr/textColorPrimary" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:paddingLeft="3dp" + android:textAppearance="@style/TextAppearance.Control.Status" + android:paddingStart="@dimen/control_status_padding" app:layout_constraintBottom_toBottomOf="@+id/icon" app:layout_constraintStart_toEndOf="@+id/status" /> @@ -60,9 +56,7 @@ android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="18sp" - android:textColor="?android:attr/textColorPrimary" - android:fontFamily="@*android:string/config_headlineFontFamily" + android:textAppearance="@style/TextAppearance.Control.Title" app:layout_constraintBottom_toTopOf="@+id/subtitle" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/icon" /> @@ -71,9 +65,15 @@ android:id="@+id/subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="16sp" - android:textColor="?android:attr/textColorSecondary" - android:fontFamily="@*android:string/config_headlineFontFamily" + android:textAppearance="@style/TextAppearance.Control.Subtitle" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> + + <CheckBox + android:id="@+id/favorite" + android:visibility="gone" + android:layout_width="48dp" + android:layout_height="48dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/controls_icon.xml b/packages/SystemUI/res/layout/controls_icon.xml new file mode 100644 index 000000000000..cc46ced0a538 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_icon.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2020, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<ImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="28dp" + android:layout_height="28dp" + android:scaleType="fitCenter" + android:layout_marginLeft="2dp" + android:layout_marginRight="2dp" /> diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml new file mode 100644 index 000000000000..a7379bedebef --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:paddingTop="@dimen/controls_management_top_padding" + android:paddingStart="@dimen/controls_management_side_padding" + android:paddingEnd="@dimen/controls_management_side_padding" > + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textSize="@dimen/controls_title_size" + android:textAlignment="center" /> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_titles_margin" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textAlignment="center" /> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:layout_marginTop="@dimen/controls_management_list_margin"> + + <ViewStub + android:id="@+id/stub" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + </androidx.core.widget.NestedScrollView> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="64dp"> + + <View + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="4dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="See other apps" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent"/> + + <Button + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="Done" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + </androidx.constraintlayout.widget.ConstraintLayout> + </FrameLayout> + + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml new file mode 100644 index 000000000000..2bab433d21f3 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_apps.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<androidx.recyclerview.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + +</androidx.recyclerview.widget.RecyclerView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml new file mode 100644 index 000000000000..a36dd1247a04 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_favorites.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:id="@+id/text_favorites" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="FAVORITES" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider1" + app:layout_constraintTop_toTopOf="parent" + /> + + <View + android:id="@+id/divider1" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listFavorites" + app:layout_constraintTop_toBottomOf="@id/text_favorites" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listFavorites" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/text_all" + app:layout_constraintTop_toBottomOf="@id/divider1"/> + + <TextView + android:id="@+id/text_all" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:text="ALL" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider2" + app:layout_constraintTop_toBottomOf="@id/listFavorites" + /> + + <View + android:id="@+id/divider2" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listAll" + app:layout_constraintTop_toBottomOf="@id/text_all" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listAll" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/divider2"/> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_no_favorites.xml b/packages/SystemUI/res/layout/controls_no_favorites.xml index 79672caed61b..3e0699dff197 100644 --- a/packages/SystemUI/res/layout/controls_no_favorites.xml +++ b/packages/SystemUI/res/layout/controls_no_favorites.xml @@ -1,18 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2020, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <TextView - android:id="@+id/controls_title" - android:text="@string/quick_controls_title" + <LinearLayout + android:id="@+id/controls_no_favorites_group" android:layout_width="match_parent" android:layout_height="wrap_content" - android:singleLine="true" - android:gravity="center" - android:textSize="25dp" + android:orientation="vertical" android:paddingTop="40dp" android:paddingBottom="40dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" - android:textColor="?android:attr/textColorPrimary" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:background="@drawable/control_no_favorites_background"/> + android:background="@drawable/control_no_favorites_background"> + + <LinearLayout + android:id="@+id/controls_icon_row" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="horizontal" + android:paddingBottom="8dp" /> + + <TextView + android:id="@+id/controls_title" + android:text="@string/quick_controls_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:layout_gravity="center" + android:textSize="25sp" + android:textColor="@*android:color/foreground_material_dark" + android:fontFamily="@*android:string/config_headlineFontFamily" /> + </LinearLayout> </merge> diff --git a/packages/SystemUI/res/layout/controls_row.xml b/packages/SystemUI/res/layout/controls_row.xml index 13a6b36accd3..4cc461a28187 100644 --- a/packages/SystemUI/res/layout/controls_row.xml +++ b/packages/SystemUI/res/layout/controls_row.xml @@ -16,7 +16,7 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - orientation="horizontal" + android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/control_spacing" /> diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 7804fe6b6272..2cd9505b8fe4 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -1,10 +1,26 @@ +<!-- + ~ 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. + --> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:paddingBottom="20dp"> <TextView android:text="@string/quick_controls_title" @@ -12,18 +28,24 @@ android:layout_height="wrap_content" android:singleLine="true" android:gravity="center" - android:textSize="25dp" - android:textColor="?android:attr/textColorPrimary" + android:textSize="25sp" + android:textColor="@*android:color/foreground_material_dark" android:fontFamily="@*android:string/config_headlineFontFamily" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent"/> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + <ImageView android:id="@+id/controls_more" android:src="@drawable/ic_more_vert" android:layout_width="34dp" android:layout_height="24dp" android:layout_marginEnd="10dp" - app:layout_constraintEnd_toEndOf="parent"/> + android:tint="@*android:color/foreground_material_dark" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/foreground_service_dungeon.xml b/packages/SystemUI/res/layout/foreground_service_dungeon.xml new file mode 100644 index 000000000000..d4e98e217213 --- /dev/null +++ b/packages/SystemUI/res/layout/foreground_service_dungeon.xml @@ -0,0 +1,61 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/foreground_service_dungeon" + android:layout_width="@dimen/qs_panel_width" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|bottom" + android:visibility="visible" +> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:orientation="vertical" + android:gravity="bottom" + android:visibility="visible" + android:background="@drawable/notif_dungeon_bg_gradient" + > + + <!-- divider view --> + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/GM2_grey_200" + android:visibility="visible" + /> + + <TextView + android:id="@+id/dungeon_title" + android:layout_height="48dp" + android:layout_width="match_parent" + android:padding="8dp" + android:text="Apps active in background" + android:textColor="@color/GM2_grey_200" + /> + + <!-- List containing the actual foreground service notifications --> + <LinearLayout + android:id="@+id/entry_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="bottom" + android:orientation="vertical" > + </LinearLayout> + + </LinearLayout> +</com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView> diff --git a/packages/SystemUI/res/layout/foreground_service_dungeon_row.xml b/packages/SystemUI/res/layout/foreground_service_dungeon_row.xml new file mode 100644 index 000000000000..a6f1638a1d89 --- /dev/null +++ b/packages/SystemUI/res/layout/foreground_service_dungeon_row.xml @@ -0,0 +1,43 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<com.android.systemui.statusbar.notification.row.DungeonRow + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/foreground_service_dungeon_row" + android:layout_width="match_parent" + android:layout_height="48dp" + android:padding="8dp" + android:clickable="true" + android:orientation="horizontal" > + + <com.android.systemui.statusbar.StatusBarIconView + android:id="@+id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:padding="4dp" /> + + <TextView + android:id="@+id/app_name" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:paddingStart="4dp" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" + android:textColor="@color/GM2_grey_200" + /> + +</com.android.systemui.statusbar.notification.row.DungeonRow> diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml index 674148495478..f90012d790e0 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -96,18 +96,18 @@ app:layout_constraintTop_toBottomOf="@id/global_actions_view"> <FrameLayout - android:translationY="@dimen/global_actions_plugin_offset" android:id="@+id/global_actions_panel_container" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout - android:translationY="@dimen/global_actions_plugin_offset" android:id="@+id/global_actions_controls" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:layout_marginRight="@dimen/global_actions_grid_horizontal_padding" + android:layout_marginLeft="@dimen/global_actions_grid_horizontal_padding" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/global_actions_panel"> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 1f7def2bc956..d0151fff95c4 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -55,10 +55,22 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:elevation="8dp" + android:elevation="@dimen/screenshot_preview_elevation" android:visibility="gone" android:background="@drawable/screenshot_rounded_corners" android:adjustViewBounds="true"/> + <FrameLayout + android:id="@+id/global_screenshot_dismiss_button" + android:layout_width="@dimen/screenshot_dismiss_button_tappable_size" + android:layout_height="@dimen/screenshot_dismiss_button_tappable_size" + android:elevation="9dp" + android:visibility="gone"> + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/screenshot_dismiss_button_margin" + android:src="@drawable/screenshot_cancel"/> + </FrameLayout> <ImageView android:id="@+id/global_screenshot_flash" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/home_controls.xml b/packages/SystemUI/res/layout/home_controls.xml deleted file mode 100644 index 69a0e872dc8c..000000000000 --- a/packages/SystemUI/res/layout/home_controls.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/home_controls_layout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="@integer/notification_panel_layout_gravity" - android:visibility="gone" - android:padding="8dp" - android:layout_margin="5dp" - android:background="?android:attr/colorBackgroundFloating"> -</FrameLayout> diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml index e91f840fe238..149446c55fc5 100644 --- a/packages/SystemUI/res/layout/media_carousel.xml +++ b/packages/SystemUI/res/layout/media_carousel.xml @@ -19,7 +19,7 @@ <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="@dimen/qs_media_height" + android:layout_height="wrap_content" android:padding="@dimen/qs_media_padding" android:scrollbars="none" android:visibility="gone" diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index a9d6e3575317..84606126086d 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -28,7 +28,7 @@ android:paddingStart="@*android:dimen/notification_content_margin_start"> <!-- Package Info --> - <RelativeLayout + <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="@dimen/notification_guts_conversation_header_height" @@ -41,16 +41,20 @@ android:layout_height="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_marginEnd="6dp" /> + android:layout_marginEnd="15dp" /> <LinearLayout android:id="@+id/names" + android:layout_weight="1" + android:layout_width="0dp" android:orientation="vertical" - android:layout_width="wrap_content" + android:layout_height="wrap_content" android:minHeight="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:gravity="center_vertical" - android:layout_toEndOf="@id/conversation_icon"> + android:layout_alignEnd="@id/conversation_icon" + android:layout_toEndOf="@id/conversation_icon" + android:layout_alignStart="@id/mute"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -107,67 +111,40 @@ android:layout_weight="1" style="@style/TextAppearance.NotificationImportanceChannel"/> </LinearLayout> + <TextView + android:id="@+id/delegate_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:text="@string/notification_delegate_header" + android:layout_toEndOf="@id/pkg_divider" + android:maxLines="1" /> </LinearLayout> - <TextView - android:id="@+id/pkg_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:layout_toEndOf="@id/name" - android:text="@*android:string/notification_header_divider_symbol" /> - <TextView - android:id="@+id/delegate_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:ellipsize="end" - android:text="@string/notification_delegate_header" - android:layout_toEndOf="@id/pkg_divider" - android:maxLines="1" /> - <!-- end aligned fields --> <ImageButton - android:id="@+id/demote" - android:layout_width="@dimen/notification_importance_toggle_size" - android:layout_height="@dimen/notification_importance_toggle_size" - android:layout_centerVertical="true" - android:background="@drawable/ripple_drawable" - android:contentDescription="@string/demote" - android:src="@drawable/ic_demote_conversation" - android:layout_toStartOf="@id/app_settings" - android:tint="@color/notification_guts_link_icon_tint"/> - <!-- Optional link to app. Only appears if the channel is not disabled and the app -asked for it --> - <ImageButton - android:id="@+id/app_settings" + android:id="@+id/mute" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" - android:visibility="gone" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_app_settings" - android:src="@drawable/ic_info" - android:layout_toStartOf="@id/info" + android:layout_toStartOf="@id/fave" android:tint="@color/notification_guts_link_icon_tint"/> <ImageButton - android:id="@+id/info" + android:id="@+id/fave" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_more_settings" - android:src="@drawable/ic_settings" android:layout_alignParentEnd="true" android:tint="@color/notification_guts_link_icon_tint"/> - </RelativeLayout> + + </LinearLayout> <LinearLayout android:id="@+id/actions" @@ -182,29 +159,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> - <Button - android:id="@+id/bubble" - android:layout_height="@dimen/notification_guts_conversation_action_height" - android:layout_width="match_parent" - style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_favorite" - android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_create_bubble" - android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" - android:drawableTint="@color/notification_guts_link_icon_tint"/> - <View - android:layout_width="match_parent" - android:layout_height="0.5dp" - android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/home" + android:id="@+id/snooze" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_home_screen" + android:text="@string/notification_menu_snooze_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_add_to_home" + android:drawableStart="@drawable/ic_snooze" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -212,12 +175,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/fave" + android:id="@+id/bubble" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_conversation_favorite" android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_create_bubble" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -226,13 +192,13 @@ asked for it --> android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/snooze" + android:id="@+id/home" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_menu_snooze_action" + android:text="@string/notification_conversation_home_screen" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_snooze" + android:drawableStart="@drawable/ic_add_to_home" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -240,14 +206,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/mute" + android:id="@+id/info" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_mute" + android:drawableStart="@drawable/ic_settings" + android:text="@string/notification_menu_settings_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_notifications_silence" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> diff --git a/packages/SystemUI/res/layout/qqs_media_panel.xml b/packages/SystemUI/res/layout/qqs_media_panel.xml index 1189371fc7f1..403b5dc3a427 100644 --- a/packages/SystemUI/res/layout/qqs_media_panel.xml +++ b/packages/SystemUI/res/layout/qqs_media_panel.xml @@ -23,20 +23,25 @@ android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" - android:padding="10dp" + android:paddingTop="16dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingBottom="12dp" + android:background="@drawable/qs_media_background" > - <!-- Top line: icon + artist name --> + <!-- Top line: icon + song name --> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clipChildren="false" android:gravity="center" + android:layout_marginBottom="12dp" > <com.android.internal.widget.CachingIconView android:id="@+id/icon" - android:layout_width="15dp" - android:layout_height="15dp" + android:layout_width="14dp" + android:layout_height="14dp" android:layout_marginEnd="5dp" /> <TextView @@ -48,15 +53,6 @@ /> </LinearLayout> - <!-- Second line: song name --> - <TextView - android:id="@+id/header_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:gravity="center"/> - <!-- Bottom section: controls --> <LinearLayout android:id="@+id/media_actions" @@ -70,8 +66,6 @@ style="@android:style/Widget.Material.Button.Borderless.Small" android:layout_width="48dp" android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" android:gravity="center" android:visibility="gone" android:id="@+id/action0" @@ -80,8 +74,6 @@ style="@android:style/Widget.Material.Button.Borderless.Small" android:layout_width="48dp" android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" android:gravity="center" android:visibility="gone" android:id="@+id/action1" @@ -90,8 +82,6 @@ style="@android:style/Widget.Material.Button.Borderless.Small" android:layout_width="48dp" android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" android:gravity="center" android:visibility="gone" android:id="@+id/action2" diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index a02962e5e1e6..0c9ce3938420 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -130,9 +130,10 @@ </LinearLayout> <View android:id="@+id/qs_drag_handle_view" - android:layout_width="24dp" + android:layout_width="48dp" android:layout_height="4dp" - android:layout_marginBottom="16dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" android:layout_gravity="center_horizontal|bottom" android:background="@drawable/qs_footer_drag_handle" /> diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml index dd422766c153..22303dc1d8d2 100644 --- a/packages/SystemUI/res/layout/qs_media_panel.xml +++ b/packages/SystemUI/res/layout/qs_media_panel.xml @@ -23,50 +23,116 @@ android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal|fill_vertical" - android:padding="10dp" + android:padding="16dp" + android:background="@drawable/qs_media_background" > - <!-- placeholder for notification header --> + <!-- Header section --> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/header" - android:padding="3dp" - android:layout_marginEnd="-12dp" + android:layout_marginBottom="16dp" + > + + <ImageView + android:id="@+id/album_art" + android:layout_width="@dimen/qs_media_album_size" + android:layout_height="@dimen/qs_media_album_size" + android:layout_marginRight="16dp" + android:layout_weight="0" /> - <!-- Top line: artist name --> - <LinearLayout - android:orientation="horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" + <LinearLayout + android:orientation="vertical" + android:layout_width="0dp" + android:layout_height="@dimen/qs_media_album_size" + android:layout_weight="1" > - <TextView - android:id="@+id/header_title" - android:layout_width="wrap_content" + <LinearLayout + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + > + <com.android.internal.widget.CachingIconView + android:id="@+id/icon" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginEnd="5dp" + /> + <TextView + android:id="@+id/app_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:singleLine="true" + /> + </LinearLayout> + + <!-- Song name --> + <TextView + android:id="@+id/header_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textSize="18sp" + android:paddingBottom="6dp" + android:gravity="center"/> + + <!-- Artist name --> + <TextView + android:id="@+id/header_artist" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:textSize="14sp" + android:singleLine="true" + /> + </LinearLayout> + + <!-- Output chip --> + <LinearLayout + android:layout_width="0dp" android:layout_height="wrap_content" - android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:singleLine="true" - /> + android:orientation="horizontal" + android:visibility="gone" + android:paddingTop="6dp" + android:paddingBottom="6dp" + android:paddingLeft="12dp" + android:paddingRight="12dp" + android:gravity="center" + android:id="@+id/media_seamless" + android:background="@*android:drawable/media_seamless_background" + android:layout_weight="1" + > + <ImageView + android:layout_width="@dimen/qs_seamless_icon_size" + android:layout_height="@dimen/qs_seamless_icon_size" + android:src="@*android:drawable/ic_media_seamless" + android:layout_marginRight="8dp" + android:id="@+id/media_seamless_image" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:text="@*android:string/ext_media_seamless_action" + android:textSize="14sp" + android:id="@+id/media_seamless_text" + android:singleLine="true" + /> + </LinearLayout> </LinearLayout> - <!-- Second line: song name --> - <TextView - android:id="@+id/header_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:gravity="center"/> - - <!-- Bottom section: controls --> + <!-- Controls --> <LinearLayout android:id="@+id/media_actions" android:orientation="horizontal" android:layoutDirection="ltr" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" > @@ -74,8 +140,8 @@ style="@android:style/Widget.Material.Button.Borderless.Small" android:layout_width="48dp" android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" android:gravity="center" android:visibility="gone" android:id="@+id/action0" @@ -84,18 +150,18 @@ style="@android:style/Widget.Material.Button.Borderless.Small" android:layout_width="48dp" android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" android:gravity="center" android:visibility="gone" android:id="@+id/action1" /> <ImageButton style="@android:style/Widget.Material.Button.Borderless.Small" - android:layout_width="48dp" - android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" + android:layout_width="52dp" + android:layout_height="52dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" android:gravity="center" android:visibility="gone" android:id="@+id/action2" @@ -104,8 +170,8 @@ style="@android:style/Widget.Material.Button.Borderless.Small" android:layout_width="48dp" android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" android:gravity="center" android:visibility="gone" android:id="@+id/action3" @@ -114,8 +180,8 @@ style="@android:style/Widget.Material.Button.Borderless.Small" android:layout_width="48dp" android:layout_height="48dp" - android:padding="8dp" - android:layout_marginEnd="2dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" android:gravity="center" android:visibility="gone" android:id="@+id/action4" diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml new file mode 100644 index 000000000000..df576d83323f --- /dev/null +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@drawable/rounded_bg_full"> + + <!-- Header --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:padding="@dimen/screenrecord_dialog_padding"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_screenrecord" + android:tint="@color/GM2_red_500" + android:layout_marginBottom="@dimen/screenrecord_dialog_padding"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="@string/screenrecord_start_label"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screenrecord_description" + android:textAppearance="?android:attr/textAppearanceSmall" + android:paddingTop="@dimen/screenrecord_dialog_padding" + android:paddingBottom="@dimen/screenrecord_dialog_padding"/> + + <!-- Options --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_mic_26dp" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_weight="0" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_weight="1"> + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_vertical" + android:text="@string/screenrecord_audio_label" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceMedium"/> + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/audio_type" + android:text="@string/screenrecord_mic_label" + android:textAppearance="?android:attr/textAppearanceSmall"/> + </LinearLayout> + <Switch + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_weight="0" + android:id="@+id/screenrecord_audio_switch"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_touch" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <Switch + android:layout_width="match_parent" + android:layout_height="48dp" + android:id="@+id/screenrecord_taps_switch" + android:text="@string/screenrecord_taps_label" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceMedium"/> + </LinearLayout> + </LinearLayout> + + <!-- hr --> + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/GM2_grey_300"/> + + <!-- Buttons --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="@dimen/screenrecord_dialog_padding"> + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="start" + android:text="@string/cancel" + style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/> + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"/> + <Button + android:id="@+id/button_start" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="end" + android:text="@string/screenrecord_start" + style="@android:style/Widget.DeviceDefault.Button.Colored"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 76c1045ef1dd..4fae3c500a45 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -42,32 +42,6 @@ android:visibility="gone" /> - <LinearLayout - android:id="@+id/divider_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal" - android:background="@color/transparent" > - - <android.widget.Space - android:layout_height="match_parent" - android:layout_width="0dp" - android:layout_weight="@integer/qqs_split_fraction" /> - - <com.android.systemui.DarkReceiverImpl - android:id="@+id/divider" - android:layout_height="match_parent" - android:layout_width="1dp" - android:layout_marginTop="4dp" - android:layout_marginBottom="4dp" /> - - <android.widget.Space - android:layout_height="match_parent" - android:layout_width="0dp" - android:layout_weight="@integer/qs_split_fraction" /> - - </LinearLayout> - <LinearLayout android:id="@+id/status_bar_contents" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 479f255b7e44..115b4a86b86d 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -55,9 +55,6 @@ android:clipChildren="false" systemui:viewType="com.android.systemui.plugins.qs.QS" /> - <!-- Temporary area to test out home controls --> - <include layout="@layout/home_controls" /> - <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout android:id="@+id/notification_stack_scroller" android:layout_marginTop="@dimen/notification_panel_margin_top" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 823685e15807..478a93de1427 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Vergrotingoorleggervenster"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Vergrotingvenster"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Vergrotingvensterkontroles"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Vinnige kontroles"</string> </resources> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index c3248249ebb6..08a655a67faa 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"የማጉያ ንብርብር መስኮት"</string> <string name="magnification_window_title" msgid="4863914360847258333">"የማጉያ መስኮት"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"የማጉያ መስኮት መቆጣጠሪያዎች"</string> + <string name="quick_controls_title" msgid="525285759614231333">"ፈጣን መቆጣጠሪያዎች"</string> </resources> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 113d705d7f31..6ceb6cdca3bc 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -984,4 +984,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"نافذة تراكب التكبير"</string> <string name="magnification_window_title" msgid="4863914360847258333">"نافذة التكبير"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"عناصر التحكم في نافذة التكبير"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 975d639c2ea4..ffefb48438c5 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"বিবৰ্ধন অ’ভাৰলে’ৰ ৱিণ্ড’"</string> <string name="magnification_window_title" msgid="4863914360847258333">"বিবৰ্ধন ৱিণ্ড’"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"বিবৰ্ধন ৱিণ্ড’ৰ নিয়ন্ত্ৰণসমূহ"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index d98d939da47a..2caffffc7ca1 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Böyütmə Üst-üstə Düşən Pəncərəsi"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Böyütmə Pəncərəsi"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Böyütmə Pəncərəsi Kontrolları"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index ff03bb9726ae..aaa35ffd7058 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -969,4 +969,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Preklopni prozor za uvećanje"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Prozor za uvećanje"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kontrole prozora za uvećanje"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Brze kontrole"</string> </resources> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 1ebbb5ea100c..619fd1c79a26 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -976,4 +976,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Акно-накладка з павелічэннем"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Акно павелічэння"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Налады акна павелічэння"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 5cfea60f901f..99c1fecef526 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Прозорец с наслагване за ниво на мащаба"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Прозорец за ниво на мащаба"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Контроли за прозореца за ниво на мащаба"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Бързи контроли"</string> </resources> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index f1e49ce57ce8..1327cf40fa55 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"ওভারলে উইন্ডো বড় করে দেখা"</string> <string name="magnification_window_title" msgid="4863914360847258333">"উইন্ডো বড় করে দেখা"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"উইন্ডো কন্ট্রোল বড় করে দেখা"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index cabe87f4a0b0..7bce70a92485 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -971,4 +971,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Preklopni prozor za uvećavanje"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Prozor za uvećavanje"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kontrole prozora za uvećavanje"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Brze kontrole"</string> </resources> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 3e257952050a..5597f51066e2 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Finestra superposada d\'ampliació"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Finestra d\'ampliació"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Finestra de controls d\'ampliació"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Controls ràpids"</string> </resources> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 3c85880eaaf8..a608c5b77735 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -974,4 +974,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Překryvné zvětšovací okno"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Zvětšovací okno"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Ovládací prvky zvětšovacího okna"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Rychlé ovládací prvky"</string> </resources> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index f2ffdaac0e37..9fb778354bda 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Vindue med overlejret forstørrelse"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Vindue med forstørrelse"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Vindue med forstørrelsesstyring"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Hurtig betjening"</string> </resources> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 58caef2b46bb..9d83e9cf55e7 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -968,4 +968,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Overlay-Vergrößerungsfenster"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Vergrößerungsfenster"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Einstellungen für Vergrößerungsfenster"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index d3686c834882..b94d10c2a741 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Παράθυρο επικάλυψης μεγέθυνσης"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Παράθυρο μεγέθυνσης"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Στοιχεία ελέγχου παραθύρου μεγέθυνσης"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Στοιχεία γρήγορου ελέγχου"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 4f61daa2bf18..df2496951be7 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification overlay window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Quick controls"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 34a2f1975d75..2d087c92b2d6 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification overlay window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Quick controls"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 4f61daa2bf18..df2496951be7 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification overlay window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Quick controls"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 4f61daa2bf18..df2496951be7 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification overlay window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Quick controls"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 2bb51311aa0a..9d4e1622ede4 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification Overlay Window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Magnification Window"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification Window Controls"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Quick Controls"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index e2bf6efa538e..33689a1ac7d2 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Ventana superpuesta de ampliación"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Ventana de ampliación"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Controles de ampliación de la ventana"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Controles rápidos"</string> </resources> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 40d7fe3bdf25..458df31e2231 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Ventana de superposición de ampliación"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Ventana de ampliación"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Ventana de controles de ampliación"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Controles rápidos"</string> </resources> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index d8f546ce48d4..4729a0d6787a 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Suurendamisakna ülekate"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Suurendamisaken"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Suurendamisakna juhtelemendid"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 9dceb5fe3f05..470a410aa88d 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Lupa-leiho gainjarria"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Lupa-leihoa"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Lupa-leihoaren aukerak"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Kontrol bizkorrak"</string> </resources> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index d27b420c0e9c..ae85a260eaf3 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"پنجره همپوشانی بزرگنمایی"</string> <string name="magnification_window_title" msgid="4863914360847258333">"پنجره بزرگنمایی"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"کنترلهای پنجره بزرگنمایی"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index cf01efd933d0..da22e1c502e5 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Suurennuksen peittoikkuna"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Suurennusikkuna"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Suurennusikkunan ohjaimet"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Pikasäätimet"</string> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 5f7a358239c7..d5d3f56286cb 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Fenêtre d\'agrandissement superposée"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Fenêtre d\'agrandissement"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Commandes pour la fenêtre d\'agrandissement"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 3c12f668d2b9..5c5d353e4103 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Fenêtre de superposition de l\'agrandissement"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Fenêtre d\'agrandissement"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Fenêtre des commandes d\'agrandissement"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 0c444e48aee3..2c0eef659a60 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Ampliación da ventá de superposición"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Ventá de superposición"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Controis de ampliación da ventá"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 9dde523d9a89..df4254ca4e0f 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"વિસ્તૃતીકરણ ઓવરલે વિંડો"</string> <string name="magnification_window_title" msgid="4863914360847258333">"વિસ્તૃતીકરણ વિંડો"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"વિસ્તૃતીકરણ વિંડોના નિયંત્રણો"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 5522605cd0f4..b62a45eb7747 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -390,8 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"बैटरी सेवर"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"शाम को चालू होगा"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"सुबह तक चालू रहेगी"</string> - <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> बजे चालू हाेगी"</string> - <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> बजे तक चालू रहेगी"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> पर चालू हाेगी"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> तक चालू रहेगी"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"एनएफ़सी"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC बंद है"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC चालू है"</string> @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification Overlay Window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"स्क्रीन को बड़ा करके दिखाने वाली विंडो"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"स्क्रीन को बड़ा करके दिखाने वाली विंडो के नियंत्रण"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 55a0e788581a..5eefc79a35f2 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -969,4 +969,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Prozor preklapanja povećavanja"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Prozor za povećavanje"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kontrole prozora za povećavanje"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Brze kontrole"</string> </resources> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index a9b2d036ebcc..5ce235dccce0 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Nagyítási fedvény ablaka"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Nagyítás ablaka"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Nagyítási vezérlők ablaka"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Gyorsvezérlők"</string> </resources> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 78acb2ed164e..4fa7096217be 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Խոշորացման պատուհանի վրադրում"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Խոշորացման պատուհան"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Խոշորացման պատուհանի կառավարման տարրեր"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Արագ կառավարման տարրեր"</string> </resources> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 95c16c87dc1a..b3db0f3fd30b 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Jendela Overlay Pembesaran"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Jendela Pembesaran"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kontrol Jendela Pembesaran"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Kontrol Cepat"</string> </resources> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 985f62d3e936..56e67ef3e0ff 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Stækkun yfirglugga"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Stækkunargluggi"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Stækkunarstillingar glugga"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Flýtistýringar"</string> </resources> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 870e6b7a75e7..423067713aa8 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Finestra overlay ingrandimento"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Finestra ingrandimento"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Finestra controlli di ingrandimento"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Controlli rapidi"</string> </resources> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 775be02e20e9..b20b10f227ba 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -974,4 +974,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"חלון ליצירת שכבת-על להגדלה"</string> <string name="magnification_window_title" msgid="4863914360847258333">"חלון הגדלה"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"בקרות של חלון ההגדלה"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 7af3f8631f03..b5189732a11b 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"拡大オーバーレイ ウィンドウ"</string> <string name="magnification_window_title" msgid="4863914360847258333">"拡大ウィンドウ"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"拡大ウィンドウ コントロール"</string> + <string name="quick_controls_title" msgid="525285759614231333">"クイック コントロール"</string> </resources> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 2433f9e32e8f..ab0dba829107 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"გადიდების გადაფარვის ფანჯარა"</string> <string name="magnification_window_title" msgid="4863914360847258333">"გადიდების ფანჯარა"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"გადიდების კონტროლის ფანჯარა"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index be93c18006b9..3c44cabb2f2f 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Ұлғайту терезесін қабаттастыру"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Ұлғайту терезесі"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Ұлғайту терезесінің басқару элементтері"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 413125cefb92..480b69490252 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"វិនដូត្រួតគ្នាលើការពង្រីក"</string> <string name="magnification_window_title" msgid="4863914360847258333">"វិនដូការពង្រីក"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"វិនដូគ្រប់គ្រងការពង្រីក"</string> + <string name="quick_controls_title" msgid="525285759614231333">"ការគ្រប់គ្រងរហ័ស"</string> </resources> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 0b98e0655a7e..c65e653f0f51 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"ವರ್ಧನೆಯ ಓವರ್ಲೇ ವಿಂಡೋ"</string> <string name="magnification_window_title" msgid="4863914360847258333">"ವರ್ಧನೆಯ ವಿಂಡೋ"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"ವರ್ಧನೆಯ ವಿಂಡೋ ನಿಯಂತ್ರಣಗಳು"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 54b055dd674b..fb5818c710dc 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"확대 오버레이 창"</string> <string name="magnification_window_title" msgid="4863914360847258333">"확대 창"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"확대 창 컨트롤"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index ed6b9186cdba..53d780ea488f 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Чоңойтуу терезесин үстүнө коюу"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Чоңойтуу терезеси"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Чоңойтуу терезесин башкаруу каражаттары"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 11c4f7c45433..48166f394289 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"ໜ້າຈໍວາງທັບການຂະຫຍາຍ"</string> <string name="magnification_window_title" msgid="4863914360847258333">"ໜ້າຈໍການຂະຫຍາຍ"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"ການຄວບຄຸມໜ້າຈໍການຂະຫຍາຍ"</string> + <string name="quick_controls_title" msgid="525285759614231333">"ການຄວບຄຸມດ່ວນ"</string> </resources> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 7da1174c0878..70bcf372d895 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -974,4 +974,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Didinimo perdangos langas"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Didinimo langas"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Didinimo lango valdikliai"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Spartieji valdikliai"</string> </resources> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index fed86b324c78..6c28b103748f 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -969,4 +969,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Palielināšanas pārklājuma logs"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Palielināšanas logs"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Palielināšanas loga vadīklas"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index a02f38ed267b..f81c5ff6fc64 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Прозорец за преклопување на зголемувањето"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Прозорец за зголемување"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Контроли на прозорец за зголемување"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Брзи контроли"</string> </resources> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 2d3f73fc5978..3ffcbc278bac 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"മാഗ്നിഫിക്കേഷൻ ഓവർലേ വിൻഡോ"</string> <string name="magnification_window_title" msgid="4863914360847258333">"മാഗ്നിഫിക്കേഷൻ വിൻഡോ"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"മാഗ്നിഫിക്കേഷൻ വിൻഡോ നിയന്ത്രണങ്ങൾ"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 85d7eadce349..87d5fbffb1bc 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Томруулалтыг давхарласан цонх"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Томруулалтын цонх"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Томруулалтын цонхны хяналт"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Шуурхай хяналтууд"</string> </resources> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 3406edf3f1bd..d67c0ef05c6d 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"मॅग्निफिकेशन ओव्हरले विंडो"</string> <string name="magnification_window_title" msgid="4863914360847258333">"मॅग्निफिकेशन विंडो"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"मॅग्निफिकेशन विंडो नियंत्रणे"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index eeaaceae0669..e3e9746d728a 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Tetingkap Tindanan Pembesaran"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Tetingkap Pembesaran"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kawalan Tetingkap Pembesaran"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 5ae82eafbcf9..99075709a62a 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"ဝင်းဒိုး ထပ်ပိုးလွှာ ချဲ့ခြင်း"</string> <string name="magnification_window_title" msgid="4863914360847258333">"ဝင်းဒိုး ချဲ့ခြင်း"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"ဝင်းဒိုး ထိန်းချုပ်မှုများ ချဲ့ခြင်း"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 463b95c0107e..c273cc9c4bf2 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Overleggsvindu for forstørring"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Forstørringsvindu"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kontroller for forstørringsvindu"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Hurtigkontroller"</string> </resources> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 21a76a3a86d0..dea3311e558e 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"म्याग्निफिकेसन ओभरले विन्डो"</string> <string name="magnification_window_title" msgid="4863914360847258333">"म्याग्निफिकेसन विन्डो"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"म्याग्निफिकेसन विन्डोका नियन्त्रणहरू"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 3c040154437c..8b015ab3c048 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Overlay voor vergrotingsvenster"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Vergrotingsvenster"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Bediening van vergrotingsvenster"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Snelle bedieningselementen"</string> </resources> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 42302756debd..e274f2720d58 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ଓଭର୍ଲେ ୱିଣ୍ଡୋ"</string> <string name="magnification_window_title" msgid="4863914360847258333">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ୱିଣ୍ଡୋ"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ୱିଣ୍ଡୋ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index f345d6a8f7aa..28da4edbe50d 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"ਵੱਡਦਰਸ਼ੀਕਰਨ ਓਵਰਲੇ Window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"ਵੱਡਦਰਸ਼ੀਕਰਨ Window"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"ਵੱਡਦਰਸ਼ੀਕਰਨ Window ਦੇ ਕੰਟਰੋਲ"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 68cb14eb32b3..fd48b9721e52 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -909,7 +909,7 @@ <string name="notification_channel_battery" msgid="9219995638046695106">"Bateria"</string> <string name="notification_channel_screenshot" msgid="7665814998932211997">"Zrzuty ekranu"</string> <string name="notification_channel_general" msgid="4384774889645929705">"Wiadomości"</string> - <string name="notification_channel_storage" msgid="2720725707628094977">"Pamięć"</string> + <string name="notification_channel_storage" msgid="2720725707628094977">"Pamięć wewnętrzna"</string> <string name="notification_channel_hints" msgid="7703783206000346876">"Wskazówki"</string> <string name="instant_apps" msgid="8337185853050247304">"Aplikacje błyskawiczne"</string> <string name="instant_apps_title" msgid="8942706782103036910">"Aplikacja <xliff:g id="APP">%1$s</xliff:g> działa"</string> @@ -974,4 +974,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Okno nakładki powiększenia"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Okno powiększenia"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Elementy sterujące okna powiększenia"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Szybkie sterowanie"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index a9101d74706f..6f616626a633 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Janela de sobreposição de ampliação"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Janela de ampliação"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Controles da janela de ampliação"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Controles rápidos"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 1a6e6739e7ff..be534ae205c4 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Janela de sobreposição da ampliação"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Janela de ampliação"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Controlos da janela de ampliação"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Controlos rápidos"</string> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index a9101d74706f..6f616626a633 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Janela de sobreposição de ampliação"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Janela de ampliação"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Controles da janela de ampliação"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Controles rápidos"</string> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index e34fa2de54d2..44940bf84a77 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -969,4 +969,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Fereastra de suprapunere pentru mărire"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Fereastra de mărire"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Comenzi pentru fereastra de mărire"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Comenzi rapide"</string> </resources> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 0ee159d0c648..6fe20944448c 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -974,4 +974,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Наложение окна увеличения"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Окно увеличения"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Настройки окна увеличения"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 2c5e1d8ba117..38933ad6c46a 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"විශාලන උඩැතිරි කවුළුව"</string> <string name="magnification_window_title" msgid="4863914360847258333">"විශාලන කවුළුව"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"විශාලනය කිරීමේ කවුළු පාලන"</string> + <string name="quick_controls_title" msgid="525285759614231333">"ඉක්මන් පාලන"</string> </resources> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 93f93a2860a9..5bd709c7142a 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -974,4 +974,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Okno prekrytia priblíženia"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Okno priblíženia"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Ovládacie prvky okna priblíženia"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Rýchle ovládacie prvky"</string> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 452827fa362f..7c06fa7da3cb 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -974,4 +974,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Prekrivno povečevalno okno"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Povečevalno okno"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kontrolniki povečevalnega okna"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Hitro upravljanje"</string> </resources> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 2dc3cc8b1cd2..7549611ec071 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Dritarja e mbivendosjes së zmadhimit"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Dritarja e zmadhimit"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kontrollet e dritares së zmadhimit"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 4127806f9cd3..6b2d971df6ed 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -969,4 +969,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Преклопни прозор за увећање"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Прозор за увећање"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Контроле прозора за увећање"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Брзе контроле"</string> </resources> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 3e29fa6e6749..33c20789dc2b 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Överlagrat förstoringsfönster"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Förstoringsfönster"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Inställningar för förstoringsfönster"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Snabbinställningar"</string> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 9fd4284710d1..bf2640f41042 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -958,10 +958,11 @@ <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Sogeza chini kushoto"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Sogeza chini kulia"</string> <string name="bubble_dismiss_text" msgid="7071770411580452911">"Ondoa"</string> - <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"Umesasisha usogezaji kwenye mfumo. Ili ufanye mabadiliko, nenda kwenye Mipangilio."</string> + <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"Umesasisha usogezaji kwenye mfumo. Ili ubadilishe, nenda kwenye Mipangilio."</string> <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"Nenda kwenye mipangilio ili usasishe usogezaji kwenye mfumo"</string> <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"Hali tuli"</string> <string name="magnification_overlay_title" msgid="6584179429612427958">"Dirisha la Kuwekelea Linalokuza"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Dirisha la Ukuzaji"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Vidhibiti vya Dirisha la Ukuzaji"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Vidhibiti vya Haraka"</string> </resources> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 55bd472a7889..c5289ec55c50 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Magnification Overlay Window"</string> <string name="magnification_window_title" msgid="4863914360847258333">"பெரிதாக்கல் சாளரம்"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"பெரிதாக்கல் சாளரக் கட்டுப்பாடுகள்"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 8b9c07ce0264..ed562f3d7377 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -71,7 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"స్క్రీన్కు నింపేలా జూమ్ చేయండి"</string> <string name="compat_mode_off" msgid="7682459748279487945">"స్క్రీన్కు నింపేలా విస్తరించండి"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"స్క్రీన్షాట్"</string> - <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"చిత్రం చొప్పించబడింది"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ఇమేజ్ చొప్పించబడింది"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"స్క్రీన్షాట్ను సేవ్ చేస్తోంది…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"స్క్రీన్షాట్ను సేవ్ చేస్తోంది…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"స్క్రీన్షాట్ సేవ్ చేయబడింది"</string> @@ -390,7 +390,7 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"బ్యాటరీ సేవర్"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"సూర్యాస్తమయానికి"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"సూర్యోదయం వరకు"</string> - <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> వద్ద"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> కు ఆన్ అవుతుంది"</string> <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> వరకు"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC నిలిపివేయబడింది"</string> @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"మాగ్నిఫికేషన్ ఓవర్లే విండో"</string> <string name="magnification_window_title" msgid="4863914360847258333">"మాగ్నిఫికేషన్ విండో"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"మాగ్నిఫికేషన్ నియంత్రణల విండో"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index dd4c3210922a..bb4999453d4f 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -20,6 +20,9 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> <resources> + <!-- SystemUIFactory component --> + <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.tv.TvSystemUIFactory</string> + <!-- SystemUI Services: The classes of the stuff to start. --> <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.util.NotificationChannels</item> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index c5e01236912d..9e5b2dbaf18b 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"หน้าต่างการขยายที่วางซ้อน"</string> <string name="magnification_window_title" msgid="4863914360847258333">"หน้าต่างการขยาย"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"การควบคุมหน้าต่างการขยาย"</string> + <string name="quick_controls_title" msgid="525285759614231333">"การควบคุมอย่างรวดเร็ว"</string> </resources> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 3d343c5b4c0b..9485a2bf3cc2 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Window ng Overlay sa Pag-magnify"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Window ng Pag-magnify"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Mga Kontrol sa Pag-magnify ng Window"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Mga Mabilisang Kontrol"</string> </resources> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 46e583fb5124..950b66b11dbf 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Yer Paylaşımlı Büyütme Penceresi"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Büyütme Penceresi"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Büyütme Penceresi Kontrolleri"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Hızlı Kontroller"</string> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 7baad0df2e55..3e1e4a96fb5c 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -974,4 +974,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Вікно збільшення з накладанням"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Вікно збільшення"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Елементи керування вікна збільшення"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Елементи швидкого керування"</string> </resources> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 97d0a5d1d2a3..6f3e7ab02ff3 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"میگنیفیکیشن اوورلے ونڈو"</string> <string name="magnification_window_title" msgid="4863914360847258333">"میگنیفکیشن ونڈو"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"میگنیفکیشن ونڈو کنٹرولز"</string> + <string name="quick_controls_title" msgid="525285759614231333">"فوری کنٹرولز"</string> </resources> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index b514f1e9754c..618463e91040 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -390,7 +390,7 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Quvvat tejash"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Kunbotarda yoqish"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Quyosh chiqqunicha"</string> - <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> da yoqish"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> da yoqiladi"</string> <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> gacha"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC o‘chiq"</string> @@ -477,9 +477,9 @@ <string name="media_projection_remember_text" msgid="6896767327140422951">"Boshqa ko‘rsatilmasin"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string> - <string name="notification_section_header_gentle" msgid="3044910806569985386">"Tovushsiz bildirishnomalar"</string> + <string name="notification_section_header_gentle" msgid="3044910806569985386">"Sokin bildirishnomalar"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Suhbatlar"</string> - <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Barcha tovushsiz bildirishnomalarni tozalash"</string> + <string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Barcha sokin bildirishnomalarni tozalash"</string> <string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bezovta qilinmasin rejimida bildirishnomalar pauza qilingan"</string> <string name="media_projection_action_text" msgid="3634906766918186440">"Boshlash"</string> <string name="empty_shade_text" msgid="8935967157319717412">"Bildirishnomalar yo‘q"</string> @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Kattalashtirish oynasining ustidan ochilishi"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Kattalashtirish oynasi"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Kattalashtirish oynasi sozlamalari"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Tezkor tugmalar"</string> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index c4d76187c817..6be05aab1583 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Cửa sổ lớp phủ phóng to"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Cửa sổ phóng to"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Các tùy chọn điều khiển cửa sổ phóng to"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 56edb64fd798..e10abbb342a0 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -964,4 +964,6 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"放大叠加窗口"</string> <string name="magnification_window_title" msgid="4863914360847258333">"放大窗口"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"放大窗口控件"</string> + <!-- no translation found for quick_controls_title (525285759614231333) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 251e23d1526b..a83ce53ca1e4 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"放大重疊視窗"</string> <string name="magnification_window_title" msgid="4863914360847258333">"放大視窗"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"放大視窗控制項"</string> + <string name="quick_controls_title" msgid="525285759614231333">"快速控制介面"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index a7ec5985a745..903259593765 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"放大重疊視窗"</string> <string name="magnification_window_title" msgid="4863914360847258333">"放大視窗"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"放大視窗控制項"</string> + <string name="quick_controls_title" msgid="525285759614231333">"快速控制項"</string> </resources> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 1203b579c52f..019128b0ab5b 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -964,4 +964,5 @@ <string name="magnification_overlay_title" msgid="6584179429612427958">"Iwindi Lembondela Lesikhulisi"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Iwindi Lesikhulisi"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Izilawuli Zewindi Lesikhulisi"</string> + <string name="quick_controls_title" msgid="525285759614231333">"Izilawuli Ezisheshayo"</string> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 2aa6d9512898..8de2df592eda 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -328,6 +328,10 @@ <item type="id" name="action_toggle_overview"/> + <!-- Whether or not to show notifications to the user. If disabled, SystemUI will still be + registered as a notification listener, but will ignore all notification events. --> + <bool name="config_renderNotifications">true</bool> + <!-- Whether or not the gear icon on notifications should be shown. The gear is shown when the the notification is not swiped enough to dismiss it. --> <bool name="config_showNotificationGear">true</bool> @@ -483,9 +487,6 @@ <!-- Whether or not to add a "people" notifications section --> <bool name="config_usePeopleFiltering">false</bool> - <!-- Package name for controls plugin --> - <string name="config_controlsPluginPackageName" translatable="false">com.android.systemui.controls.panel</string> - <!-- Defines the blacklist for system icons. That is to say, the icons in the status bar that are part of the blacklist are never displayed. Each item in the blacklist must be a string defined in core/res/res/config.xml to properly blacklist the icon. diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cc58b2014496..0db7d67efd8d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -103,7 +103,8 @@ <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item> <dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen> - <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen> + <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end + </dimen> <!-- max height of a notification such that the content can still fade out when closing --> <dimen name="max_notification_fadeout_height">100dp</dimen> @@ -267,7 +268,9 @@ <dimen name="status_bar_icon_drawing_size">15dp</dimen> <!-- size at which Notification icons will be drawn on Ambient Display --> - <dimen name="status_bar_icon_drawing_size_dark">@*android:dimen/notification_header_icon_size_ambient</dimen> + <dimen name="status_bar_icon_drawing_size_dark"> + @*android:dimen/notification_header_icon_size_ambient + </dimen> <!-- size of notification icons when the notifications are hidden --> <dimen name="hidden_shelf_icon_size">16dp</dimen> @@ -299,8 +302,11 @@ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="global_screenshot_x_scale">80dp</dimen> + <dimen name="screenshot_preview_elevation">8dp</dimen> <dimen name="screenshot_offset_y">48dp</dimen> <dimen name="screenshot_offset_x">16dp</dimen> + <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen> + <dimen name="screenshot_dismiss_button_margin">8dp</dimen> <dimen name="screenshot_action_container_offset_y">32dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> <dimen name="screenshot_action_container_padding_vertical">10dp</dimen> @@ -597,10 +603,14 @@ <!-- The height of the divider between the individual notifications in a notification group. --> - <dimen name="notification_children_container_divider_height">@dimen/notification_divider_height</dimen> + <dimen name="notification_children_container_divider_height"> + @dimen/notification_divider_height + </dimen> <!-- The top margin for the notification children container in its non-expanded form. --> - <dimen name="notification_children_container_margin_top">@*android:dimen/notification_content_margin_top</dimen> + <dimen name="notification_children_container_margin_top"> + @*android:dimen/notification_content_margin_top + </dimen> <!-- The height of a notification header --> <dimen name="notification_header_height">53dp</dimen> @@ -972,7 +982,6 @@ <!-- Global actions power menu --> <dimen name="global_actions_top_margin">12dp</dimen> - <dimen name="global_actions_plugin_offset">-145dp</dimen> <dimen name="global_actions_panel_width">120dp</dimen> <dimen name="global_actions_padding">12dp</dimen> @@ -1062,9 +1071,12 @@ <integer name="wireless_charging_scale_dots_duration">83</integer> <integer name="wireless_charging_num_dots">16</integer> <!-- Starting text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">0</item> + <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen"> + 0 + </item> <!-- Ending text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24</item> + <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24 + </item> <!-- time until battery info is at full opacity--> <integer name="wireless_charging_anim_opacity_offset">80</integer> <!-- duration batteryLevel opacity goes from 0 to 1 duration --> @@ -1091,7 +1103,7 @@ <!-- Blur radius on status bar window and power menu --> <dimen name="min_window_blur_radius">1px</dimen> - <dimen name="max_window_blur_radius">100px</dimen> + <dimen name="max_window_blur_radius">150px</dimen> <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> @@ -1165,10 +1177,12 @@ <dimen name="new_qs_vertical_margin">8dp</dimen> <!-- Size of media cards in the QSPanel carousel --> - <dimen name="qs_media_height">150dp</dimen> <dimen name="qs_media_width">350dp</dimen> <dimen name="qs_media_padding">8dp</dimen> <dimen name="qs_media_corner_radius">10dp</dimen> + <dimen name="qs_media_album_size">72dp</dimen> + <dimen name="qs_seamless_icon_size">20dp</dimen> + <dimen name="qqs_media_spacing">8dp</dimen> <dimen name="magnification_border_size">5dp</dimen> <dimen name="magnification_frame_move_short">5dp</dimen> @@ -1182,11 +1196,33 @@ <dimen name="magnifier_up_down_controls_height">40dp</dimen> <!-- Home Controls --> - <dimen name="control_spacing">5dp</dimen> + <dimen name="control_spacing">4dp</dimen> <dimen name="control_corner_radius">15dp</dimen> <dimen name="control_height">100dp</dimen> <dimen name="control_padding">15dp</dimen> - <dimen name="control_status_normal">12dp</dimen> - <dimen name="control_status_expanded">18dp</dimen> - <dimen name="app_icon_size">32dp</dimen> + <dimen name="control_status_normal">12sp</dimen> + <dimen name="control_status_expanded">18sp</dimen> + <dimen name="control_base_item_margin">2dp</dimen> + <dimen name="control_status_padding">3dp</dimen> + + <!-- Home Controls management screens --> + <dimen name="controls_management_top_padding">48dp</dimen> + <dimen name="controls_management_side_padding">8dp</dimen> + <dimen name="controls_management_titles_margin">8dp</dimen> + <dimen name="controls_management_list_margin">16dp</dimen> + <dimen name="controls_title_size">26sp</dimen> + + <dimen name="controls_app_icon_size">32dp</dimen> + <dimen name="controls_app_icon_frame_side_padding">8dp</dimen> + <dimen name="controls_app_icon_frame_top_padding">4dp</dimen> + <dimen name="controls_app_bottom_margin">8dp</dimen> + <dimen name="controls_app_text_padding">8dp</dimen> + <dimen name="controls_app_divider_height">2dp</dimen> + <dimen name="controls_app_divider_side_margin">32dp</dimen> + + <dimen name="controls_card_margin">2dp</dimen> + + <!-- Screen Record --> + <dimen name="screenrecord_dialog_padding">18dp</dimen> + <dimen name="screenrecord_logo_size">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8d935ecc691d..ebaf55fd7e9c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -196,7 +196,7 @@ <!-- text to show in place of RemoteInput images when they cannot be shown. [CHAR LIMIT=50] --> - <string name="remote_input_image_insertion_text">Image inserted</string> + <string name="remote_input_image_insertion_text">sent an image</string> <!-- Notification ticker displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=30] --> <string name="screenshot_saving_ticker">Saving screenshot\u2026</string> @@ -217,15 +217,31 @@ your organization</string> <!-- Notification title displayed for screen recording [CHAR LIMIT=50]--> - <string name="screenrecord_name">Screen Recording</string> + <string name="screenrecord_name">Screen Recorder</string> <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]--> <string name="screenrecord_channel_description">Ongoing notification for a screen record session</string> - <!-- Label for the button to begin screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_start_label">Start Recording</string> - <!-- Label for the checkbox to enable microphone input during screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_mic_label">Record voiceover</string> + <!-- Title for the screen prompting the user to begin recording their screen [CHAR LIMIT=NONE]--> + <string name="screenrecord_start_label">Start Recording?</string> + <!-- Message reminding the user that sensitive information may be captured during a screen recording [CHAR_LIMIT=NONE]--> + <string name="screenrecord_description">While recording, Android System can capture any sensitive information that\u2019s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio.</string> + <!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]--> + <string name="screenrecord_audio_label">Record audio</string> + <!-- Label for the option to record audio from the device [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_label">Device audio</string> + <!-- Description of what audio may be captured from the device [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_description">Sound from your device, like music, calls, and ringtones</string> + <!-- Label for the option to enable microphone input during screen recording [CHAR LIMIT=NONE]--> + <string name="screenrecord_mic_label">Microphone</string> + <!-- Label for an option to record audio from both device and microphone [CHAR LIMIT=NONE]--> + <string name="screenrecord_device_audio_and_mic_label">Device audio and microphone</string> + <!-- Button to start a screen recording [CHAR LIMIT=50]--> + <string name="screenrecord_start">Start</string> + <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]--> + <string name="screenrecord_ongoing_screen_only">Recording screen</string> + <!-- Notification text displayed when we are recording both the screen and audio [CHAR LIMIT=100]--> + <string name="screenrecord_ongoing_screen_and_audio">Recording screen and audio</string> <!-- Label for the checkbox to enable showing location of touches during screen recording [CHAR LIMIT=NONE]--> - <string name="screenrecord_taps_label">Show taps</string> + <string name="screenrecord_taps_label">Show touches on screen</string> <!-- Label for notification that the user can tap to stop and save the screen recording [CHAR LIMIT=NONE] --> <string name="screenrecord_stop_text">Tap to stop</string> <!-- Label for notification action to stop and save the screen recording [CHAR LIMIT=35] --> @@ -250,6 +266,8 @@ <string name="screenrecord_delete_error">Error deleting screen recording</string> <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] --> <string name="screenrecord_permission_error">Failed to get permissions</string> + <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] --> + <string name="screenrecord_start_error">Error starting screen recording</string> <!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] --> <string name="usb_preference_title">USB file transfer options</string> @@ -1811,23 +1829,23 @@ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification --> <string name="demote">Mark this notification as not a conversation</string> - <!-- [CHAR LIMIT=100] Mark this conversation as a favorite --> - <string name="notification_conversation_favorite">Favorite</string> + <!-- [CHAR LIMIT=100] This conversation is marked as important --> + <string name="notification_conversation_favorite">Important conversation</string> - <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite --> - <string name="notification_conversation_unfavorite">Unfavorite</string> + <!-- [CHAR LIMIT=100] This conversation is not marked as important --> + <string name="notification_conversation_unfavorite">Not an important conversation</string> - <!-- [CHAR LIMIT=100] Mute this conversation --> - <string name="notification_conversation_mute">Mute</string> + <!-- [CHAR LIMIT=100] This conversation is silenced (will not make sound or vibrate)--> + <string name="notification_conversation_mute">Silenced</string> - <!-- [CHAR LIMIT=100] Umute this conversation --> - <string name="notification_conversation_unmute">Unmute</string> + <!-- [CHAR LIMIT=100] This conversation is alerting (may make sound and/or vibrate)--> + <string name="notification_conversation_unmute">Alerting</string> <!-- [CHAR LIMIT=100] Show notification as bubble --> - <string name="notification_conversation_bubble">Show as bubble</string> + <string name="notification_conversation_bubble">Show bubble</string> <!-- [CHAR LIMIT=100] Turn off bubbles for notification --> - <string name="notification_conversation_unbubble">Turn off bubbles</string> + <string name="notification_conversation_unbubble">Remove bubbles</string> <!-- [CHAR LIMIT=100] Add this conversation to home screen --> <string name="notification_conversation_home_screen">Add to home screen</string> @@ -1842,7 +1860,10 @@ <string name="notification_menu_snooze_description">notification snooze options</string> <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> - <string name="notification_menu_snooze_action">Snooze</string> + <string name="notification_menu_snooze_action">Remind me</string> + + <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> + <string name="notification_menu_settings_action">Settings</string> <!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]--> <string name="snooze_undo">UNDO</string> @@ -2547,9 +2568,24 @@ <!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] --> <string name="magnification_controls_title">Magnification Window Controls</string> - <!-- Quick Controls strings [CHAR LIMIT=30] --> + <!-- Quick Controls strings --> + <!-- Quick Controls view header [CHAR LIMIT=30] --> <string name="quick_controls_title">Quick Controls</string> - <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] --> - <string name="tile_unavailable">Unvailable</string> + <!-- Controls management providers screen title [CHAR LIMIT=30]--> + <string name="controls_providers_title">Add Controls</string> + <!-- Controls management providers screen subtitle [CHAR LIMIT=NONE] --> + <string name="controls_providers_subtitle">Choose an app from which to add controls</string> + <!-- Number of favorites for controls management screen [CHAR LIMIT=NONE]--> + <plurals name="controls_number_of_favorites"> + <item quantity="one"><xliff:g id="number" example="1">%s</xliff:g> current favorite.</item> + <item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> current favorites.</item> + </plurals> + + <!-- Controls management controls screen default title [CHAR LIMIT=30] --> + <string name="controls_favorite_default_title">Controls</string> + <!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] --> + <string name="controls_favorite_subtitle">Choose controls for quick access</string> + + </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 10f59a423805..bcffa8d0feea 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -234,6 +234,37 @@ <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/> + <style name="TextAppearance.AuthCredential"> + <item name="android:gravity">center_horizontal</item> + <item name="android:fontFamily">google-sans</item> + <item name="android:textAlignment">gravity</item> + <item name="android:layout_gravity">top</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="TextAppearance.AuthCredential.Title"> + <item name="android:layout_marginBottom">2dp</item> + <item name="android:layout_marginLeft">24dp</item> + <item name="android:layout_marginRight">24dp</item> + <item name="android:layout_marginTop">16dp</item> + <item name="android:textSize">24sp</item> + </style> + + <style name="TextAppearance.AuthCredential.Description"> + <item name="android:layout_marginBottom">12dp</item> + <item name="android:layout_marginStart">40dp</item> + <item name="android:layout_marginEnd">40dp</item> + <item name="android:layout_marginTop">3dp</item> + <item name="android:textSize">16sp</item> + </style> + + <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:gravity">center</item> + <item name="android:singleLine">true</item> + <item name="android:textColor">?android:attr/colorForeground</item> + <item name="android:textSize">24sp</item> + </style> + <style name="DeviceManagementDialogTitle"> <item name="android:gravity">center</item> <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item> @@ -297,6 +328,16 @@ <item name="android:textColor">?attr/wallpaperTextColor</item> </style> + <style name="LockPatternContainerStyle"> + <item name="android:maxHeight">400dp</item> + <item name="android:maxWidth">420dp</item> + <item name="android:minHeight">0dp</item> + <item name="android:minWidth">0dp</item> + <item name="android:paddingBottom">0dp</item> + <item name="android:paddingHorizontal">44dp</item> + <item name="android:paddingTop">0dp</item> + </style> + <style name="LockPatternStyle"> <item name="*android:regularColor">?attr/wallpaperTextColor</item> <item name="*android:successColor">?attr/wallpaperTextColor</item> @@ -583,4 +624,28 @@ <item name="android:background">?android:attr/selectableItemBackground</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + + <!-- Controls styles --> + <style name="Theme.ControlsManagement" parent="@android:style/Theme.DeviceDefault.NoActionBar"> + <item name="android:windowIsTranslucent">false</item> + </style> + + <style name="TextAppearance.Control"> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + </style> + + <style name="TextAppearance.Control.Status"> + <item name="android:textSize">12sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="TextAppearance.Control.Title"> + <item name="android:textSize">14sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + <style name="TextAppearance.Control.Subtitle"> + <item name="android:textSize">12sp</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> + </style> + </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java index a37861068e48..b8997c29dd52 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -135,8 +135,7 @@ public class PluginInstanceManager<T extends Plugin> { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo info : plugins) { if (className.startsWith(info.mPackage)) { - disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); - disableAny = true; + disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); } } return disableAny; @@ -144,10 +143,11 @@ public class PluginInstanceManager<T extends Plugin> { public boolean disableAll() { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); + boolean disabledAny = false; for (int i = 0; i < plugins.size(); i++) { - disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); + disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } - return plugins.size() != 0; + return disabledAny; } private boolean isPluginWhitelisted(ComponentName pluginName) { @@ -166,7 +166,7 @@ public class PluginInstanceManager<T extends Plugin> { return false; } - private void disable(PluginInfo info, @PluginEnabler.DisableReason int reason) { + private boolean disable(PluginInfo info, @PluginEnabler.DisableReason int reason) { // Live by the sword, die by the sword. // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. @@ -176,10 +176,12 @@ public class PluginInstanceManager<T extends Plugin> { // assuming one of them must be bad. if (isPluginWhitelisted(pluginComponent)) { // Don't disable whitelisted plugins as they are a part of the OS. - return; + return false; } Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); mManager.getPluginEnabler().setDisabled(pluginComponent, reason); + + return true; } public <T> boolean dependsOn(Plugin p, Class<T> cls) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IconLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IconLoader.java deleted file mode 100644 index 78b1b26140d5..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IconLoader.java +++ /dev/null @@ -1,197 +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.shared.recents.model; - -import static android.content.pm.PackageManager.MATCH_ANY_USER; - -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; -import android.util.IconDrawableFactory; -import android.util.Log; -import android.util.LruCache; - -import com.android.systemui.shared.system.PackageManagerWrapper; - -public abstract class IconLoader { - - private static final String TAG = "IconLoader"; - - protected final Context mContext; - protected final TaskKeyLruCache<Drawable> mIconCache; - protected final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; - - public IconLoader(Context context, TaskKeyLruCache<Drawable> iconCache, LruCache<ComponentName, - ActivityInfo> activityInfoCache) { - mContext = context; - mIconCache = iconCache; - mActivityInfoCache = activityInfoCache; - } - - /** - * Returns the activity info for the given task key, retrieving one from the system if the - * task key is expired. - * - * TODO: Move this to an ActivityInfoCache class - */ - public ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) { - ComponentName cn = taskKey.getComponent(); - ActivityInfo activityInfo = mActivityInfoCache.get(cn); - if (activityInfo == null) { - activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, taskKey.userId); - if (cn == null || activityInfo == null) { - Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " + - activityInfo); - return null; - } - mActivityInfoCache.put(cn, activityInfo); - } - return activityInfo; - } - - public Drawable getIcon(Task t) { - Drawable cachedIcon = mIconCache.get(t.key); - if (cachedIcon == null) { - cachedIcon = createNewIconForTask(t.key, t.taskDescription, true /* returnDefault */); - mIconCache.put(t.key, cachedIcon); - } - return cachedIcon; - } - - /** - * Returns the cached task icon if the task key is not expired, updating the cache if it is. - */ - public Drawable getAndInvalidateIfModified(Task.TaskKey taskKey, - ActivityManager.TaskDescription td, boolean loadIfNotCached) { - // Return the cached activity icon if it exists - Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey); - if (icon != null) { - return icon; - } - - if (loadIfNotCached) { - icon = createNewIconForTask(taskKey, td, false /* returnDefault */); - if (icon != null) { - mIconCache.put(taskKey, icon); - return icon; - } - } - - // We couldn't load any icon - return null; - } - - private Drawable createNewIconForTask(Task.TaskKey taskKey, - ActivityManager.TaskDescription desc, boolean returnDefault) { - int userId = taskKey.userId; - Bitmap tdIcon = desc.getInMemoryIcon(); - if (tdIcon != null) { - return createDrawableFromBitmap(tdIcon, userId, desc); - } - if (desc.getIconResource() != 0) { - try { - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo appInfo = pm.getApplicationInfo(taskKey.getPackageName(), - MATCH_ANY_USER); - Resources res = pm.getResourcesForApplication(appInfo); - return createBadgedDrawable(res.getDrawable(desc.getIconResource(), null), userId, - desc); - } catch (Resources.NotFoundException|PackageManager.NameNotFoundException e) { - Log.e(TAG, "Could not find icon drawable from resource", e); - } - } - - tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( - desc.getIconFilename(), userId); - if (tdIcon != null) { - return createDrawableFromBitmap(tdIcon, userId, desc); - } - - // Load the icon from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); - if (activityInfo != null) { - Drawable icon = getBadgedActivityIcon(activityInfo, userId, desc); - if (icon != null) { - return icon; - } - } - - // At this point, even if we can't load the icon, we will set the default icon. - return returnDefault ? getDefaultIcon(userId) : null; - } - - public abstract Drawable getDefaultIcon(int userId); - - protected Drawable createDrawableFromBitmap(Bitmap icon, int userId, - ActivityManager.TaskDescription desc) { - return createBadgedDrawable( - new BitmapDrawable(mContext.getResources(), icon), userId, desc); - } - - protected abstract Drawable createBadgedDrawable(Drawable icon, int userId, - ActivityManager.TaskDescription desc); - - /** - * @return the activity icon for the ActivityInfo for a user, badging if necessary. - */ - protected abstract Drawable getBadgedActivityIcon(ActivityInfo info, int userId, - ActivityManager.TaskDescription desc); - - public static class DefaultIconLoader extends IconLoader { - - private final BitmapDrawable mDefaultIcon; - private final IconDrawableFactory mDrawableFactory; - - public DefaultIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache, - LruCache<ComponentName, ActivityInfo> activityInfoCache) { - super(context, iconCache, activityInfoCache); - - // Create the default assets - Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); - icon.eraseColor(0); - mDefaultIcon = new BitmapDrawable(context.getResources(), icon); - mDrawableFactory = IconDrawableFactory.newInstance(context); - } - - @Override - public Drawable getDefaultIcon(int userId) { - return mDefaultIcon; - } - - @Override - protected Drawable createBadgedDrawable(Drawable icon, int userId, - ActivityManager.TaskDescription desc) { - if (userId != UserHandle.myUserId()) { - icon = mContext.getPackageManager().getUserBadgedIcon(icon, new UserHandle(userId)); - } - return icon; - } - - @Override - protected Drawable getBadgedActivityIcon(ActivityInfo info, int userId, - ActivityManager.TaskDescription desc) { - return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId); - } - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java deleted file mode 100644 index 8a244bf81c7c..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java +++ /dev/null @@ -1,101 +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.shared.recents.model; - -import android.util.Log; -import android.util.SparseArray; - -import com.android.systemui.shared.recents.model.Task.TaskKey; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Base class for both strong and LRU task key cache. - */ -public abstract class TaskKeyCache<V> { - - protected static final String TAG = "TaskKeyCache"; - - protected final SparseArray<TaskKey> mKeys = new SparseArray<>(); - - /** - * Gets a specific entry in the cache with the specified key, regardless of whether the cached - * value is valid or not. - */ - public final synchronized V get(TaskKey key) { - return getCacheEntry(key.id); - } - - /** - * Returns the value only if the key is valid (has not been updated since the last time it was - * in the cache) - */ - public final synchronized V getAndInvalidateIfModified(TaskKey key) { - TaskKey lastKey = mKeys.get(key.id); - if (lastKey != null) { - if ((lastKey.windowingMode != key.windowingMode) || - (lastKey.lastActiveTime != key.lastActiveTime)) { - // The task has updated (been made active since the last time it was put into the - // LRU cache) or the stack id for the task has changed, invalidate that cache item - remove(key); - return null; - } - } - // Either the task does not exist in the cache, or the last active time is the same as - // the key specified, so return what is in the cache - return getCacheEntry(key.id); - } - - /** Puts an entry in the cache for a specific key. */ - public final synchronized void put(TaskKey key, V value) { - if (key == null || value == null) { - Log.e(TAG, "Unexpected null key or value: " + key + ", " + value); - return; - } - mKeys.put(key.id, key); - putCacheEntry(key.id, value); - } - - - /** Removes a cache entry for a specific key. */ - public final synchronized void remove(TaskKey key) { - // Remove the key after the cache value because we need it to make the callback - removeCacheEntry(key.id); - mKeys.remove(key.id); - } - - /** @return {@link Collection} of {@link TaskKey} */ - public Collection<TaskKey> getValues() { - Collection<TaskKey> result = new ArrayList<>(mKeys.size()); - for (int i = 0; i < mKeys.size(); i++) { - result.add(mKeys.valueAt(i)); - } - return result; - } - - /** Removes all the entries in the cache. */ - public final synchronized void evictAll() { - evictAllCache(); - mKeys.clear(); - } - - protected abstract V getCacheEntry(int id); - protected abstract void putCacheEntry(int id, V value); - protected abstract void removeCacheEntry(int id); - protected abstract void evictAllCache(); -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java deleted file mode 100644 index bc57b08236cf..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.recents.model; - -import android.util.LruCache; - -import com.android.systemui.shared.recents.model.Task.TaskKey; - -import java.io.PrintWriter; - -/** - * A mapping of {@link TaskKey} to value, with additional LRU functionality where the least - * recently referenced key/values will be evicted as more values than the given cache size are - * inserted. - * - * In addition, this also allows the caller to invalidate cached values for keys that have since - * changed. - */ -public class TaskKeyLruCache<V> extends TaskKeyCache<V> { - - public interface EvictionCallback { - void onEntryEvicted(TaskKey key); - } - - private final LruCache<Integer, V> mCache; - private final EvictionCallback mEvictionCallback; - - public TaskKeyLruCache(int cacheSize) { - this(cacheSize, null); - } - - public TaskKeyLruCache(int cacheSize, EvictionCallback evictionCallback) { - mEvictionCallback = evictionCallback; - mCache = new LruCache<Integer, V>(cacheSize) { - - @Override - protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) { - if (mEvictionCallback != null && evicted) { - mEvictionCallback.onEntryEvicted(mKeys.get(taskId)); - } - - // Only remove from mKeys on cache remove, not a cache update. - if (newV == null) { - mKeys.remove(taskId); - } - } - }; - } - - /** Trims the cache to a specific size */ - public final void trimToSize(int cacheSize) { - mCache.trimToSize(cacheSize); - } - - public void dump(String prefix, PrintWriter writer) { - String innerPrefix = prefix + " "; - - writer.print(prefix); writer.print(TAG); - writer.print(" numEntries="); writer.print(mKeys.size()); - writer.println(); - int keyCount = mKeys.size(); - for (int i = 0; i < keyCount; i++) { - writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i))); - } - } - - @Override - protected V getCacheEntry(int id) { - return mCache.get(id); - } - - @Override - protected void putCacheEntry(int id, V value) { - mCache.put(id, value); - } - - @Override - protected void removeCacheEntry(int id) { - mCache.remove(id); - } - - @Override - protected void evictAllCache() { - mCache.evictAll(); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java index 557845c34bf2..356e0ca7193c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java @@ -40,7 +40,7 @@ import java.util.function.Consumer; * @param <R> The proto class type of the entry root proto in the buffer */ public class FrameProtoTracer<P, S extends P, T extends P, R> - implements TraceBuffer.ProtoProvider<P, S, T>, Choreographer.FrameCallback { + implements Choreographer.FrameCallback { private static final String TAG = "FrameProtoTracer"; private static final int BUFFER_CAPACITY = 1024 * 1024; @@ -57,6 +57,25 @@ public class FrameProtoTracer<P, S extends P, T extends P, R> private volatile boolean mEnabled; private boolean mFrameScheduled; + private final TraceBuffer.ProtoProvider<P, S, T> mProvider = + new TraceBuffer.ProtoProvider<P, S, T>() { + @Override + public int getItemSize(P proto) { + return mParams.getProtoSize(proto); + } + + @Override + public byte[] getBytes(P proto) { + return mParams.getProtoBytes(proto); + } + + @Override + public void write(S encapsulatingProto, Queue<T> buffer, OutputStream os) + throws IOException { + os.write(mParams.serializeEncapsulatingProto(encapsulatingProto, buffer)); + } + }; + public interface ProtoTraceParams<P, S, T, R> { File getTraceFile(); S getEncapsulatingTraceProto(); @@ -68,7 +87,7 @@ public class FrameProtoTracer<P, S extends P, T extends P, R> public FrameProtoTracer(ProtoTraceParams<P, S, T, R> params) { mParams = params; - mBuffer = new TraceBuffer<>(BUFFER_CAPACITY, this, new Consumer<T>() { + mBuffer = new TraceBuffer<>(BUFFER_CAPACITY, mProvider, new Consumer<T>() { @Override public void accept(T t) { onProtoDequeued(t); @@ -78,21 +97,6 @@ public class FrameProtoTracer<P, S extends P, T extends P, R> mChoreographer = Choreographer.getMainThreadInstance(); } - @Override - public int getItemSize(P proto) { - return mParams.getProtoSize(proto); - } - - @Override - public byte[] getBytes(P proto) { - return mParams.getProtoBytes(proto); - } - - @Override - public void write(S encapsulatingProto, Queue<T> buffer, OutputStream os) throws IOException { - os.write(mParams.serializeEncapsulatingProto(encapsulatingProto, buffer)); - } - public void start() { synchronized (mLock) { if (mEnabled) { diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java index b2423b9bf252..85724a969fed 100644 --- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java +++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java @@ -27,7 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; @@ -47,7 +47,6 @@ public class AdminSecondaryLockScreenController { private Handler mHandler; private IKeyguardClient mClient; private KeyguardSecurityCallback mKeyguardCallback; - private SurfaceControl.Transaction mTransaction; private final ServiceConnection mConnection = new ServiceConnection() { @Override @@ -84,13 +83,13 @@ public class AdminSecondaryLockScreenController { } @Override - public void onSurfaceControlCreated(@Nullable SurfaceControl remoteSurfaceControl) { + public void onRemoteContentReady( + @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) { if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); } - if (remoteSurfaceControl != null) { - mTransaction.reparent(remoteSurfaceControl, mView.getSurfaceControl()) - .apply(); + if (surfacePackage != null) { + mView.setChildSurfacePackage(surfacePackage); } else { dismiss(KeyguardUpdateMonitor.getCurrentUser()); } @@ -138,11 +137,10 @@ public class AdminSecondaryLockScreenController { public AdminSecondaryLockScreenController(Context context, ViewGroup parent, KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback, - Handler handler, SurfaceControl.Transaction transaction) { + Handler handler) { mContext = context; mHandler = handler; mParent = parent; - mTransaction = transaction; mUpdateMonitor = updateMonitor; mKeyguardCallback = callback; mView = new AdminSecurityView(mContext, mSurfaceHolderCallback); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java index ed1cd8191092..d5a08dda9853 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -409,15 +409,6 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { mAudioManager.dispatchMediaKeyEvent(keyEvent); } - @Override - public void dispatchSystemUiVisibilityChanged(int visibility) { - super.dispatchSystemUiVisibilityChanged(visibility); - - if (!(mContext instanceof Activity)) { - setSystemUiVisibility(STATUS_BAR_DISABLE_BACK); - } - } - /** * In general, we enable unlocking the insecure keyguard with the menu key. However, there are * some cases where we wish to disable it, notably when the menu button placement or technology diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index ae787260adca..ba8a1a945a77 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -15,7 +15,12 @@ */ package com.android.keyguard; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.sNewInsetsMode; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static java.lang.Integer.max; import android.app.Activity; import android.app.AlertDialog; @@ -34,10 +39,10 @@ import android.util.Slog; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.SurfaceControl; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowInsets; import android.view.WindowManager; import android.widget.FrameLayout; @@ -143,8 +148,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe mViewConfiguration = ViewConfiguration.get(context); mKeyguardStateController = Dependency.get(KeyguardStateController.class); mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this, - mUpdateMonitor, mCallback, new Handler(Looper.myLooper()), - new SurfaceControl.Transaction()); + mUpdateMonitor, mCallback, new Handler(Looper.myLooper())); } public void setSecurityCallback(SecurityCallback callback) { @@ -339,13 +343,22 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } @Override - protected boolean fitSystemWindows(Rect insets) { + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + // Consume bottom insets because we're setting the padding locally (for IME and navbar.) - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), insets.bottom); - insets.bottom = 0; - return false; + int inset; + if (sNewInsetsMode == NEW_INSETS_MODE_FULL) { + int bottomInset = insets.getInsetsIgnoringVisibility(systemBars()).bottom; + int imeInset = insets.getInsets(ime()).bottom; + inset = max(bottomInset, imeInset); + } else { + inset = insets.getSystemWindowInsetBottom(); + } + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), inset); + return insets.inset(0, 0, 0, inset); } + private void showDialog(String title, String message) { if (mAlertDialog != null) { mAlertDialog.dismiss(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index f61f585cfe7c..f48210cf90c4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -42,7 +42,6 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.animation.Animation; -import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; @@ -251,9 +250,9 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe SliceItem item = rc.getSliceItem(); final Uri itemTag = item.getSlice().getUri(); // Try to reuse the view if already exists in the layout - KeyguardSliceButton button = mRow.findViewWithTag(itemTag); + KeyguardSliceTextView button = mRow.findViewWithTag(itemTag); if (button == null) { - button = new KeyguardSliceButton(mContext); + button = new KeyguardSliceTextView(mContext); button.setTextColor(blendedColor); button.setTag(itemTag); final int viewIndex = i - (mHasHeader ? 1 : 0); @@ -316,8 +315,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe int childCount = mRow.getChildCount(); for (int i = 0; i < childCount; i++) { View v = mRow.getChildAt(i); - if (v instanceof Button) { - ((Button) v).setTextColor(blendedColor); + if (v instanceof TextView) { + ((TextView) v).setTextColor(blendedColor); } } } @@ -500,8 +499,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - if (child instanceof KeyguardSliceButton) { - ((KeyguardSliceButton) child).setMaxWidth(width / childCount); + if (child instanceof KeyguardSliceTextView) { + ((KeyguardSliceTextView) child).setMaxWidth(width / childCount); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -527,13 +526,13 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe * Representation of an item that appears under the clock on main keyguard message. */ @VisibleForTesting - static class KeyguardSliceButton extends Button implements + static class KeyguardSliceTextView extends TextView implements ConfigurationController.ConfigurationListener { @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary; - public KeyguardSliceButton(Context context) { + KeyguardSliceTextView(Context context) { super(context, null /* attrs */, 0 /* styleAttr */, sStyleId); onDensityOrFontScaleChanged(); setEllipsize(TruncateAt.END); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 4e2f7d4ee862..e5f6d3c90eb7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -21,15 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_STOPPED; import static android.content.Intent.ACTION_USER_UNLOCKED; -import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; -import static android.os.BatteryManager.BATTERY_STATUS_FULL; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; -import static android.os.BatteryManager.EXTRA_HEALTH; -import static android.os.BatteryManager.EXTRA_LEVEL; -import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT; -import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE; -import static android.os.BatteryManager.EXTRA_PLUGGED; -import static android.os.BatteryManager.EXTRA_STATUS; import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM; @@ -67,7 +59,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.media.AudioManager; -import android.os.BatteryManager; import android.os.CancellationSignal; import android.os.Handler; import android.os.IRemoteCallback; @@ -94,6 +85,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.DejankUtils; import com.android.systemui.DumpController; import com.android.systemui.Dumpable; @@ -1059,29 +1051,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab MSG_TIMEZONE_UPDATE, intent.getStringExtra("time-zone")); mHandler.sendMessage(msg); } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { - final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); - final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0); - final int level = intent.getIntExtra(EXTRA_LEVEL, 0); - final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); - final int maxChargingMicroAmp = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); - int maxChargingMicroVolt = intent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); - final int maxChargingMicroWatt; - - 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. - maxChargingMicroWatt = (maxChargingMicroAmp / 1000) - * (maxChargingMicroVolt / 1000); - } else { - maxChargingMicroWatt = -1; - } final Message msg = mHandler.obtainMessage( - MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health, - maxChargingMicroWatt)); + MSG_BATTERY_UPDATE, new BatteryStatus(intent)); mHandler.sendMessage(msg); } else if (Intent.ACTION_SIM_STATE_CHANGED.equals(action)) { SimData args = SimData.fromIntent(intent); @@ -1323,82 +1295,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - public static class BatteryStatus { - public static final int CHARGING_UNKNOWN = -1; - public static final int CHARGING_SLOWLY = 0; - public static final int CHARGING_REGULAR = 1; - public static final int CHARGING_FAST = 2; - - public final int status; - public final int level; - public final int plugged; - public final int health; - public final int maxChargingWattage; - - public BatteryStatus(int status, int level, int plugged, int health, - int maxChargingWattage) { - this.status = status; - this.level = level; - this.plugged = plugged; - this.health = health; - this.maxChargingWattage = maxChargingWattage; - } - - /** - * Determine whether the device is plugged in (USB, power, or wireless). - * - * @return true if the device is plugged in. - */ - public boolean isPluggedIn() { - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB - || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; - } - - /** - * Determine whether the device is plugged in (USB, power). - * - * @return true if the device is plugged in wired (as opposed to wireless) - */ - public boolean isPluggedInWired() { - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB; - } - - /** - * Whether or not the device is charged. Note that some devices never return 100% for - * battery level, so this allows either battery level or status to determine if the - * battery is charged. - * - * @return true if the device is charged - */ - public boolean isCharged() { - return status == BATTERY_STATUS_FULL || level >= 100; - } - - /** - * Whether battery is low and needs to be charged. - * - * @return true if battery is low - */ - public boolean isBatteryLow() { - return level < LOW_BATTERY_THRESHOLD; - } - - public final int getChargingSpeed(int slowThreshold, int fastThreshold) { - return maxChargingWattage <= 0 ? CHARGING_UNKNOWN : - maxChargingWattage < slowThreshold ? CHARGING_SLOWLY : - maxChargingWattage > fastThreshold ? CHARGING_FAST : - CHARGING_REGULAR; - } - - @Override - public String toString() { - return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged - + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}"; - } - } - public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { private final Consumer<Integer> mStrongAuthRequiredChangedCallback; @@ -1883,9 +1779,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep; final int user = getCurrentUser(); final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); - final boolean isLockOutOrLockDown = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) + final boolean isLockDown = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); final boolean isEncryptedOrTimedOut = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) @@ -1899,9 +1794,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass; // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing. - // Lockout/lockdown modes shouldn't scan, since they are more explicit. + // Lock-down mode shouldn't scan, since it is more explicit. boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass && !mBouncer) - && !isLockOutOrLockDown; + && !isLockDown; // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -2641,9 +2536,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private boolean refreshSimState(int subId, int slotId) { final TelephonyManager tele = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); int state = (tele != null) ? - tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN; + tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN; SimData data = mSimDatas.get(subId); final boolean changed; if (data == null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 8e87b7ad45b9..49f72a925a0e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,6 +23,7 @@ import android.os.SystemClock; import android.telephony.TelephonyManager; import android.view.WindowManagerPolicyConstants; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.statusbar.KeyguardIndicationController; import java.util.TimeZone; @@ -42,7 +43,7 @@ public class KeyguardUpdateMonitorCallback { * * @param status current battery status */ - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { } + public void onRefreshBatteryInfo(BatteryStatus status) { } /** * Called once per minute or when the time changes. diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java index 97578e19ccd1..3fce55f6e515 100644 --- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java +++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java @@ -61,19 +61,21 @@ public class DejankUtils { || !isMainThread() || sTemporarilyIgnoreStrictMode) { return null; } + } - try { - String description = binder.getInterfaceDescriptor(); + try { + String description = binder.getInterfaceDescriptor(); + synchronized (sLock) { if (sWhitelistedFrameworkClasses.contains(description)) { return null; } - } catch (RemoteException e) { - e.printStackTrace(); } - - StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek()); - return null; + } catch (RemoteException e) { + e.printStackTrace(); } + + StrictMode.noteSlowCall("IPC detected on critical path: " + sBlockingIpcs.peek()); + return null; } @Override @@ -126,9 +128,11 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && sBlockingIpcs.empty()) { synchronized (sLock) { sBlockingIpcs.push("detectBlockingIpcs"); - try { - runnable.run(); - } finally { + } + try { + runnable.run(); + } finally { + synchronized (sLock) { sBlockingIpcs.pop(); } } @@ -177,9 +181,11 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; - try { - runnable.run(); - } finally { + } + try { + runnable.run(); + } finally { + synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } } @@ -196,14 +202,16 @@ public class DejankUtils { if (STRICT_MODE_ENABLED && !sTemporarilyIgnoreStrictMode) { synchronized (sLock) { sTemporarilyIgnoreStrictMode = true; - final T val; - try { - val = supplier.get(); - } finally { + } + final T val; + try { + val = supplier.get(); + } finally { + synchronized (sLock) { sTemporarilyIgnoreStrictMode = false; } - return val; } + return val; } else { return supplier.get(); } diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index eab970626bf1..924d16dd27d7 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -89,7 +89,7 @@ public class ForegroundServiceNotificationListener { } @Override - public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) { + public void onEntryRemoved(NotificationEntry entry, int reason) { removeNotification(entry.getSbn()); } }); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 044c5a027dac..7dea7f83f0c6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -664,6 +664,7 @@ public abstract class AuthBiometricView extends LinearLayout { setTextOrHide(mSubtitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE)); + setTextOrHide(mDescriptionView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index b8d32aec30e3..8a492a83b3df 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -87,7 +87,7 @@ public class AuthContainerView extends LinearLayout @VisibleForTesting @Nullable AuthBiometricView mBiometricView; @VisibleForTesting @Nullable AuthCredentialView mCredentialView; - private final ImageView mBackgroundView; + @VisibleForTesting final ImageView mBackgroundView; @VisibleForTesting final ScrollView mBiometricScrollView; private final View mPanelView; @@ -333,6 +333,12 @@ public class AuthContainerView extends LinearLayout throw new IllegalStateException("Unknown credential type: " + credentialType); } + // The background is used for detecting taps / cancelling authentication. Since the + // credential view is full-screen and should not be canceled from background taps, + // disable it. + mBackgroundView.setOnClickListener(null); + mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + mCredentialView.setContainerView(this); mCredentialView.setEffectiveUserId(mEffectiveUserId); mCredentialView.setCredentialType(credentialType); @@ -583,11 +589,13 @@ public class AuthContainerView extends LinearLayout * @return */ public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) { + final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_SECURE; final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + windowFlags, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("BiometricPrompt"); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index 8f2cf70a8184..68b05e358786 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import android.content.Context; +import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricPrompt; import android.os.AsyncTask; import android.os.Bundle; @@ -28,6 +29,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.accessibility.AccessibilityManager; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -56,6 +58,7 @@ public abstract class AuthCredentialView extends LinearLayout { private TextView mTitleView; private TextView mSubtitleView; private TextView mDescriptionView; + private ImageView mIconView; protected TextView mErrorView; protected @Utils.CredentialType int mCredentialType; @@ -176,6 +179,16 @@ public abstract class AuthCredentialView extends LinearLayout { setTextOrHide(mDescriptionView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); + final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId); + final Drawable image; + if (isManagedProfile) { + image = getResources().getDrawable(R.drawable.auth_dialog_enterprise, + mContext.getTheme()); + } else { + image = getResources().getDrawable(R.drawable.auth_dialog_lock, mContext.getTheme()); + } + mIconView.setImageDrawable(image); + // Only animate this if we're transitioning from a biometric view. if (mShouldAnimateContents) { setTranslationY(getResources() @@ -207,6 +220,7 @@ public abstract class AuthCredentialView extends LinearLayout { mTitleView = findViewById(R.id.title); mSubtitleView = findViewById(R.id.subtitle); mDescriptionView = findViewById(R.id.description); + mIconView = findViewById(R.id.icon); mErrorView = findViewById(R.id.error); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index e954163d51aa..05838abe184a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -16,7 +16,6 @@ package com.android.systemui.bubbles; -import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; import static android.app.Notification.FLAG_BUBBLE; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; @@ -44,19 +43,13 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; -import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.RemoteInput; import android.content.Context; -import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.ShortcutManager; import android.content.res.Configuration; import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.service.notification.NotificationListenerService.RankingMap; @@ -75,25 +68,33 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.util.ScreenshotHelper; +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.DumpController; +import com.android.systemui.Dumpable; import com.android.systemui.R; +import com.android.systemui.bubbles.BubbleController.BubbleExpandListener; +import com.android.systemui.bubbles.BubbleController.BubbleStateChangeListener; +import com.android.systemui.bubbles.BubbleController.NotifCallback; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PinnedStackListenerForwarder; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.WindowManagerWrapper; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +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 com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.ZenModeController; import java.io.FileDescriptor; @@ -101,10 +102,8 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Singleton; @@ -116,7 +115,7 @@ import javax.inject.Singleton; * The controller manages addition, removal, and visible state of bubbles on screen. */ @Singleton -public class BubbleController implements ConfigurationController.ConfigurationListener { +public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; @@ -140,14 +139,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final Context mContext; private final NotificationEntryManager mNotificationEntryManager; + private final NotifPipeline mNotifPipeline; private final BubbleTaskStackListener mTaskStackListener; private BubbleStateChangeListener mStateChangeListener; private BubbleExpandListener mExpandListener; @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final NotificationGroupManager mNotificationGroupManager; private final ShadeController mShadeController; - private final RemoteInputUriController mRemoteInputUriController; - private Handler mHandler = new Handler() {}; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; @@ -171,7 +169,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final NotificationShadeWindowController mNotificationShadeWindowController; private final ZenModeController mZenModeController; private StatusBarStateListener mStatusBarStateListener; - private final ScreenshotHelper mScreenshotHelper; // Callback that updates BubbleOverflowActivity on data change. @Nullable private Runnable mOverflowCallback = null; @@ -217,16 +214,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * Listener for handling bubble screenshot events. - */ - public interface BubbleScreenshotListener { - /** - * Called to trigger taking a screenshot and sending the result to a bubble. - */ - void onBubbleScreenshot(Bubble bubble); - } - - /** * Listener to be notified when a bubbles' notification suppression state changes. */ public interface NotificationSuppressionChangedListener { @@ -243,16 +230,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi */ public interface NotifCallback { /** - * Called when the BubbleController wants to remove an entry that it was previously hiding - * from the shade. See {@link BubbleController#isBubbleNotificationSuppressedFromShade}. + * Called when a bubbled notification that was hidden from the shade is now being removed + * This can happen when an app cancels a bubbled notification or when the user dismisses a + * bubble. */ - void removeNotification(NotificationEntry entry); + void removeNotification(NotificationEntry entry, int reason); /** * Called when a bubbled notification has changed whether it should be * filtered from the shade. */ - void invalidateNotificationFilter(String reason); + void invalidateNotifications(String reason); /** * Called on a bubbled entry that has been removed when there are no longer @@ -301,11 +289,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager, NotificationEntryManager entryManager, - RemoteInputUriController remoteInputUriController) { + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { this(context, notificationShadeWindowController, statusBarStateController, shadeController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager, entryManager, - remoteInputUriController); + notifPipeline, featureFlags, dumpController); } public BubbleController(Context context, @@ -320,13 +310,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotificationLockscreenUserManager notifUserManager, NotificationGroupManager groupManager, NotificationEntryManager entryManager, - RemoteInputUriController remoteInputUriController) { + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { + dumpController.registerDumpable(TAG, this); mContext = context; mShadeController = shadeController; mNotificationInterruptionStateProvider = interruptionStateProvider; mNotifUserManager = notifUserManager; mZenModeController = zenModeController; - mRemoteInputUriController = remoteInputUriController; mZenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { @@ -364,7 +356,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mNotificationEntryManager = entryManager; mNotificationGroupManager = groupManager; - setupNEM(); + mNotifPipeline = notifPipeline; + + if (!featureFlags.isNewNotifPipelineRenderingEnabled()) { + setupNEM(); + } else { + setupNotifPipeline(); + } mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarStateListener = new StatusBarStateListener(); @@ -399,7 +397,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mUserCreatedBubbles = new HashSet<>(); mUserBlockedBubbles = new HashSet<>(); - mScreenshotHelper = new ScreenshotHelper(context); mBubbleIconFactory = new BubbleIconFactory(context); } @@ -414,7 +411,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mNotificationEntryManager.addNotificationEntryListener( new NotificationEntryListener() { @Override - public void onNotificationAdded(NotificationEntry entry) { + public void onPendingEntryAdded(NotificationEntry entry) { onEntryAdded(entry); } @@ -424,18 +421,46 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } @Override + public void onEntryRemoved( + NotificationEntry entry, + @android.annotation.Nullable NotificationVisibility visibility, + boolean removedByUser) { + BubbleController.this.onEntryRemoved(entry); + } + + @Override public void onNotificationRankingUpdated(RankingMap rankingMap) { onRankingUpdated(rankingMap); } }); - mNotificationEntryManager.setNotificationRemoveInterceptor( + mNotificationEntryManager.addNotificationRemoveInterceptor( new NotificationRemoveInterceptor() { @Override - public boolean onNotificationRemoveRequested(String key, int reason) { - NotificationEntry entry = - mNotificationEntryManager.getActiveNotificationUnfiltered(key); - return shouldInterceptDismissal(entry, reason); + public boolean onNotificationRemoveRequested( + String key, + NotificationEntry entry, + int dismissReason) { + final boolean isClearAll = dismissReason == REASON_CANCEL_ALL; + final boolean isUserDimiss = dismissReason == REASON_CANCEL + || dismissReason == REASON_CLICK; + final boolean isAppCancel = dismissReason == REASON_APP_CANCEL + || dismissReason == REASON_APP_CANCEL_ALL; + final boolean isSummaryCancel = + dismissReason == REASON_GROUP_SUMMARY_CANCELED; + + // Need to check for !appCancel here because the notification may have + // previously been dismissed & entry.isRowDismissed would still be true + boolean userRemovedNotif = + (entry != null && entry.isRowDismissed() && !isAppCancel) + || isClearAll || isUserDimiss || isSummaryCancel; + + if (userRemovedNotif || isUserCreatedBubble(key) + || isSummaryOfUserCreatedBubble(entry)) { + return handleDismissalInterception(entry); + } + + return false; } }); @@ -459,13 +484,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi addNotifCallback(new NotifCallback() { @Override - public void removeNotification(NotificationEntry entry) { + public void removeNotification(NotificationEntry entry, int reason) { mNotificationEntryManager.performRemoveNotification(entry.getSbn(), - UNDEFINED_DISMISS_REASON); + reason); } @Override - public void invalidateNotificationFilter(String reason) { + public void invalidateNotifications(String reason) { mNotificationEntryManager.updateNotifications(reason); } @@ -473,18 +498,28 @@ public class BubbleController implements ConfigurationController.ConfigurationLi public void maybeCancelSummary(NotificationEntry entry) { // Check if removed bubble has an associated suppressed group summary that needs // to be removed now. - final String groupKey = entry.getSbn().getGroup(); + final String groupKey = entry.getSbn().getGroupKey(); if (mBubbleData.isSummarySuppressed(groupKey)) { - mBubbleData.removeSuppressedSummary(entry.getSbn().getGroupKey()); + mBubbleData.removeSuppressedSummary(groupKey); final NotificationEntry summary = mNotificationEntryManager.getActiveNotificationUnfiltered( mBubbleData.getSummaryKey(groupKey)); - mNotificationEntryManager.performRemoveNotification(summary.getSbn(), - UNDEFINED_DISMISS_REASON); + if (summary != null) { + mNotificationEntryManager.performRemoveNotification(summary.getSbn(), + UNDEFINED_DISMISS_REASON); + } } - // Check if summary should be removed from NoManGroup + // Check if we still need to remove the summary from NoManGroup because the summary + // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above. + // For example: + // 1. Bubbled notifications (group) is posted to shade and are visible bubbles + // 2. User expands bubbles so now their respective notifications in the shade are + // hidden, including the group summary + // 3. User removes all bubbles + // 4. We expect all the removed bubbles AND the summary (note: the summary was + // never added to the suppressedSummary list in BubbleData, so we add this check) NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry.getSbn()); if (summary != null) { @@ -501,6 +536,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }); } + private void setupNotifPipeline() { + mNotifPipeline.addCollectionListener(new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + BubbleController.this.onEntryAdded(entry); + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + BubbleController.this.onEntryUpdated(entry); + } + + @Override + public void onRankingUpdate(RankingMap rankingMap) { + onRankingUpdated(rankingMap); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, + @NotifCollection.CancellationReason int reason) { + BubbleController.this.onEntryRemoved(entry); + } + }); + } + /** * Sets whether to perform inflation on the same thread as the caller. This method should only * be used in tests, not in production. @@ -537,9 +597,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } - if (mBubbleScreenshotListener != null) { - mStackView.setBubbleScreenshotListener(mBubbleScreenshotListener); - } } } @@ -784,7 +841,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi Log.d(TAG, "onUserDemotedBubble: " + entry.getKey()); } entry.setFlagBubble(false); - removeBubble(entry.getKey(), DISMISS_BLOCKED); + removeBubble(entry, DISMISS_BLOCKED); mUserCreatedBubbles.remove(entry.getKey()); if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble( mContext, entry.getSbn().getPackageName())) { @@ -801,17 +858,29 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return mUserCreatedBubbles.contains(key); } + boolean isSummaryOfUserCreatedBubble(NotificationEntry entry) { + if (isSummaryOfBubbles(entry)) { + List<Bubble> bubbleChildren = + mBubbleData.getBubblesInGroup(entry.getSbn().getGroupKey()); + for (int i = 0; i < bubbleChildren.size(); i++) { + // Check if any are user-created (i.e. experimental bubbles) + if (isUserCreatedBubble(bubbleChildren.get(i).getKey())) { + return true; + } + } + } + return false; + } + /** - * Removes the bubble associated with the {@param uri}. + * Removes the bubble with the given NotificationEntry. * <p> * Must be called from the main thread. */ @MainThread - void removeBubble(String key, int reason) { - // TEMP: refactor to change this to pass entry - Bubble bubble = mBubbleData.getBubbleWithKey(key); - if (bubble != null) { - mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason); + void removeBubble(NotificationEntry entry, int reason) { + if (mBubbleData.hasBubbleWithKey(entry.getKey())) { + mBubbleData.notificationEntryRemoved(entry, reason); } } @@ -841,7 +910,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi && (canLaunchInActivityView(mContext, entry) || wasAdjusted); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it - removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); + removeBubble(entry, DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble) { if (wasAdjusted && !previouslyUserCreated) { // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated @@ -851,6 +920,21 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } + private void onEntryRemoved(NotificationEntry entry) { + if (isSummaryOfBubbles(entry)) { + final String groupKey = entry.getSbn().getGroupKey(); + mBubbleData.removeSuppressedSummary(groupKey); + + // Remove any associated bubble children with the summary + final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + for (int i = 0; i < bubbleChildren.size(); i++) { + removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED); + } + } else { + removeBubble(entry, DISMISS_NOTIF_CANCEL); + } + } + private void onRankingUpdated(RankingMap rankingMap) { // Forward to BubbleData to block any bubbles which should no longer be shown mBubbleData.notificationRankingUpdated(rankingMap); @@ -878,7 +962,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi final Bubble bubble = removed.first; @DismissReason final int reason = removed.second; mStackView.removeBubble(bubble); - // If the bubble is removed for user switching, leave the notification in place. if (reason != DISMISS_USER_CHANGED) { if (!mBubbleData.hasBubbleWithKey(bubble.getKey()) @@ -886,7 +969,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { - cb.removeNotification(bubble.getEntry()); + cb.removeNotification(bubble.getEntry(), REASON_CANCEL); } } else { // Update the flag for SysUI @@ -940,7 +1023,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } for (NotifCallback cb : mCallbacks) { - cb.invalidateNotificationFilter("BubbleData.Listener.applyUpdate"); + cb.invalidateNotifications("BubbleData.Listener.applyUpdate"); } updateStack(); @@ -962,124 +1045,85 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }; /** - * We intercept notification entries cancelled by the user (i.e. dismissed) when there is an - * active bubble associated with it. We do this so that developers can still cancel it - * (and hence the bubbles associated with it). However, these intercepted notifications - * should then be hidden from the shade since the user has cancelled them, so we update - * {@link Bubble#showInShade}. + * We intercept notification entries (including group summaries) dismissed by the user when + * there is an active bubble associated with it. We do this so that developers can still + * cancel it (and hence the bubbles associated with it). However, these intercepted + * notifications should then be hidden from the shade since the user has cancelled them, so we + * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add + * {@link BubbleData#addSummaryToSuppress}. * - * The cancellation of summaries with children associated with bubbles are also handled in this - * method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}. - * - * @return true if we want to intercept the dismissal of the entry, else false + * @return true if we want to intercept the dismissal of the entry, else false. */ - public boolean shouldInterceptDismissal(NotificationEntry entry, int dismissReason) { + public boolean handleDismissalInterception(NotificationEntry entry) { if (entry == null) { return false; } - String key = entry.getKey(); - String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); - boolean inBubbleData = mBubbleData.hasBubbleWithKey(key); - boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) - && mBubbleData.getSummaryKey(groupKey).equals(key)); - boolean isSummary = entry != null - && entry.getSbn().getNotification().isGroupSummary(); - boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary) - && bubbleChildren != null && !bubbleChildren.isEmpty(); + final boolean interceptBubbleDismissal = mBubbleData.hasBubbleWithKey(entry.getKey()) + && entry.isBubble(); + final boolean interceptSummaryDismissal = isSummaryOfBubbles(entry); - if (!inBubbleData && !isSummaryOfBubbles) { + if (interceptSummaryDismissal) { + handleSummaryDismissalInterception(entry); + } else if (interceptBubbleDismissal) { + Bubble bubble = mBubbleData.getBubbleWithKey(entry.getKey()); + bubble.setSuppressNotification(true); + bubble.setShowDot(false /* show */, true /* animate */); + } else { return false; } - final boolean isClearAll = dismissReason == REASON_CANCEL_ALL; - final boolean isUserDimiss = dismissReason == REASON_CANCEL - || dismissReason == REASON_CLICK; - final boolean isAppCancel = dismissReason == REASON_APP_CANCEL - || dismissReason == REASON_APP_CANCEL_ALL; - final boolean isSummaryCancel = dismissReason == REASON_GROUP_SUMMARY_CANCELED; - - // Need to check for !appCancel here because the notification may have - // previously been dismissed & entry.isRowDismissed would still be true - boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel) - || isClearAll || isUserDimiss || isSummaryCancel; - if (isSummaryOfBubbles) { - return handleSummaryRemovalInterception(entry, userRemovedNotif); + // Update the shade + for (NotifCallback cb : mCallbacks) { + cb.invalidateNotifications("BubbleController.handleDismissalInterception"); } + return true; + } - // The bubble notification sticks around in the data as long as the bubble is - // not dismissed and the app hasn't cancelled the notification. - Bubble bubble = mBubbleData.getBubbleWithKey(key); - boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif; - if (bubbleExtended) { - bubble.setSuppressNotification(true); - bubble.setShowDot(false /* show */, true /* animate */); - for (NotifCallback cb : mCallbacks) { - cb.invalidateNotificationFilter("BubbleController" - + ".shouldInterceptDismissal"); - } - return true; - } else if (!userRemovedNotif && entry != null - && !isUserCreatedBubble(bubble.getKey())) { - // This wasn't a user removal so we should remove the bubble as well - mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL); + private boolean isSummaryOfBubbles(NotificationEntry entry) { + if (entry == null) { return false; } - return false; - } - private boolean handleSummaryRemovalInterception(NotificationEntry summary, - boolean userRemovedNotif) { - String groupKey = summary.getSbn().getGroupKey(); + String groupKey = entry.getSbn().getGroupKey(); ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) + && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey())); + boolean isSummary = entry.getSbn().getNotification().isGroupSummary(); + return (isSuppressedSummary || isSummary) + && bubbleChildren != null + && !bubbleChildren.isEmpty(); + } - if (userRemovedNotif) { - // If it's a user dismiss we mark the children to be hidden from the shade. - for (int i = 0; i < bubbleChildren.size(); i++) { - Bubble bubbleChild = bubbleChildren.get(i); - // As far as group manager is concerned, once a child is no longer shown - // in the shade, it is essentially removed. - mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); - bubbleChild.setSuppressNotification(true); - bubbleChild.setShowDot(false /* show */, true /* animate */); - } - // And since all children are removed, remove the summary. - mNotificationGroupManager.onEntryRemoved(summary); - - // If the summary was auto-generated we don't need to keep that notification around - // because apps can't cancel it; so we only intercept & suppress real summaries. - boolean isAutogroupSummary = (summary.getSbn().getNotification().flags - & FLAG_AUTOGROUP_SUMMARY) != 0; - if (!isAutogroupSummary) { - // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated - mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(), - summary.getKey()); - // Tell shade to update for the suppression - mNotificationEntryManager.updateNotifications("BubbleController" - + ".handleSummaryRemovalInterception"); - } - return !isAutogroupSummary; - } else { - // If it's not a user dismiss it's a cancel. - for (int i = 0; i < bubbleChildren.size(); i++) { - // First check if any of these are user-created (i.e. experimental bubbles) - if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) { - // Experimental bubble! Intercept the removal. - return true; + private void handleSummaryDismissalInterception(NotificationEntry summary) { + // current children in the row: + final List<NotificationEntry> children = summary.getChildren(); + if (children != null) { + for (int i = 0; i < children.size(); i++) { + NotificationEntry child = children.get(i); + if (mBubbleData.hasBubbleWithKey(child.getKey())) { + // Suppress the bubbled child + // As far as group manager is concerned, once a child is no longer shown + // in the shade, it is essentially removed. + Bubble bubbleChild = mBubbleData.getBubbleWithKey(child.getKey()); + mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); + bubbleChild.setSuppressNotification(true); + bubbleChild.setShowDot(false /* show */, true /* animate */); + } else { + // non-bubbled children can be removed + for (NotifCallback cb : mCallbacks) { + cb.removeNotification(child, REASON_GROUP_SUMMARY_CANCELED); + } } } - - // Not an experimental bubble, safe to remove. - mBubbleData.removeSuppressedSummary(groupKey); - // Remove any associated bubble children with the summary. - for (int i = 0; i < bubbleChildren.size(); i++) { - Bubble bubbleChild = bubbleChildren.get(i); - mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(), - DISMISS_GROUP_CANCELLED); - } - return false; } + + // And since all children are removed, remove the summary. + mNotificationGroupManager.onEntryRemoved(summary); + + // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated + mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(), + summary.getKey()); } /** @@ -1268,68 +1312,4 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } } - - // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic. - private Intent prepareRemoteInputFromData(String contentType, Uri data, - RemoteInput remoteInput, NotificationEntry entry) { - HashMap<String, Uri> results = new HashMap<>(); - results.put(contentType, data); - mRemoteInputUriController.grantInlineReplyUriPermission(entry.getSbn(), data); - Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - RemoteInput.addDataResultToIntent(remoteInput, fillInIntent, results); - - return fillInIntent; - } - - // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic. - private void sendRemoteInput(Intent intent, NotificationEntry entry, - PendingIntent pendingIntent) { - // Tell ShortcutManager that this package has been "activated". ShortcutManager - // will reset the throttling for this package. - // Strictly speaking, the intent receiver may be different from the notification publisher, - // but that's an edge case, and also because we can't always know which package will receive - // an intent, so we just reset for the publisher. - mContext.getSystemService(ShortcutManager.class).onApplicationActive( - entry.getSbn().getPackageName(), - entry.getSbn().getUser().getIdentifier()); - - try { - pendingIntent.send(mContext, 0, intent); - } catch (PendingIntent.CanceledException e) { - Log.i(TAG, "Unable to send remote input result", e); - } - } - - private void sendScreenshotToBubble(Bubble bubble) { - mScreenshotHelper.takeScreenshot( - android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true /* hasStatus */, - true /* hasNav */, - mHandler, - new Consumer<Uri>() { - @Override - public void accept(Uri uri) { - if (uri != null) { - NotificationEntry entry = bubble.getEntry(); - Pair<RemoteInput, Notification.Action> pair = entry.getSbn() - .getNotification().findRemoteInputActionPair(false); - if (pair != null) { - RemoteInput remoteInput = pair.first; - Notification.Action action = pair.second; - Intent dataIntent = prepareRemoteInputFromData("image/png", uri, - remoteInput, entry); - sendRemoteInput(dataIntent, entry, action.actionIntent); - mBubbleData.setSelectedBubble(bubble); - mBubbleData.setExpanded(true); - } else { - Log.w(TAG, "No RemoteInput found for notification: " - + entry.getSbn().getKey()); - } - } - } - }); - } - - private final BubbleScreenshotListener mBubbleScreenshotListener = - bubble -> sendScreenshotToBubble(bubble); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index fa1392644735..0d5261dcb7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -19,9 +19,9 @@ package com.android.systemui.bubbles; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.view.Display.INVALID_DISPLAY; - import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.sNewInsetsMode; + import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -56,6 +56,7 @@ import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.AlphaOptimizedButton; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Container for the expanded bubble view, handles rendering the caret and settings icon. @@ -146,7 +147,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList // the bubble again so we'll just remove it. Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() + ", " + e.getMessage() + "; removing bubble"); - mBubbleController.removeBubble(getBubbleKey(), + mBubbleController.removeBubble(getBubbleEntry(), BubbleController.DISMISS_INVALID_INTENT); } }); @@ -190,7 +191,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } if (mBubble != null && !mBubbleController.isUserCreatedBubble(mBubble.getKey())) { // Must post because this is called from a binder thread. - post(() -> mBubbleController.removeBubble(mBubble.getKey(), + post(() -> mBubbleController.removeBubble(mBubble.getEntry(), BubbleController.DISMISS_TASK_FINISHED)); } } @@ -279,6 +280,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList return mBubble != null ? mBubble.getKey() : "null"; } + private NotificationEntry getBubbleEntry() { + return mBubble != null ? mBubble.getEntry() : null; + } + void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes( new int[] { @@ -448,12 +453,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList int bottomInset = getRootWindowInsets() != null ? getRootWindowInsets().getStableInsetBottom() : 0; - int mh = mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight + return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight - mPointerMargin - bottomInset; - Log.i(TAG, "max exp height: " + mh); -// return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight -// - mPointerMargin - bottomInset; - return mh; } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java index 006de8406ce2..20b3386b450d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java @@ -73,9 +73,6 @@ public class BubbleExperimentConfig { private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps"; - private static final String ALLOW_BUBBLE_MENU = "allow_bubble_screenshot_menu"; - private static final boolean ALLOW_BUBBLE_MENU_DEFAULT = false; - private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow"; private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false; @@ -137,16 +134,6 @@ public class BubbleExperimentConfig { * When true, show a menu when a bubble is long-pressed, which will allow the user to take * actions on that bubble. */ - static boolean allowBubbleScreenshotMenu(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - ALLOW_BUBBLE_MENU, - ALLOW_BUBBLE_MENU_DEFAULT ? 1 : 0) != 0; - } - - /** - * When true, show a menu when a bubble is long-pressed, which will allow the user to take - * actions on that bubble. - */ static boolean allowBubbleOverflow(Context context) { return Settings.Secure.getInt(context.getContentResolver(), ALLOW_BUBBLE_OVERFLOW, diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 4194352f93bd..5b9ea7dd5e3a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -431,7 +431,7 @@ public class BubbleFlyoutView extends FrameLayout { final float interpolatedRadius = getInterpolatedRadius(); rectPath.addRoundRect(mBgRect, interpolatedRadius, interpolatedRadius, Path.Direction.CW); - outline.setConvexPath(rectPath); + outline.setPath(rectPath); // Get rid of the triangle path once it has disappeared behind the flyout. if (mPercentStillFlyout > 0.5f) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java deleted file mode 100644 index bf8306561f1b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java +++ /dev/null @@ -1,84 +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.bubbles; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.android.systemui.R; - -/** - * Menu which allows users to take actions on bubbles, ex. screenshots. - */ -public class BubbleMenuView extends FrameLayout { - private FrameLayout mMenu; - private boolean mShowing = false; - - /** Delay before taking a screenshot once the button is tapped to allow the menu time to hide.*/ - public static final long SCREENSHOT_DELAY = 200; - - public BubbleMenuView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BubbleMenuView(Context context) { - super(context); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mMenu = findViewById(R.id.bubble_menu_view); - ImageView icon = findViewById(com.android.internal.R.id.icon); - icon.setImageDrawable(mContext.getDrawable(com.android.internal.R.drawable.ic_screenshot)); - } - - /** - * Get the bubble menu view. - */ - public View getMenuView() { - return mMenu; - } - - /** - * Checks whether the bubble menu is currently displayed. - */ - public boolean isShowing() { - return mShowing; - } - - /** - * Show the bubble menu at the specified position on the screen. - */ - public void show(float x, float y) { - mShowing = true; - this.setVisibility(VISIBLE); - mMenu.setTranslationX(x); - mMenu.setTranslationY(y); - } - - /** - * Hide the bubble menu. - */ - public void hide() { - mShowing = false; - this.setVisibility(GONE); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index bea55c820b40..2d55a1ddf654 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -146,7 +146,6 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V int viewType) { BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.bubble_view, parent, false); - view.setPadding(15, 15, 15, 15); return new ViewHolder(view); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 6062a3d45be0..bce172b89187 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -115,7 +115,6 @@ public class BubbleStackView extends FrameLayout { /** How long to wait, in milliseconds, before hiding the flyout. */ @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; - private BubbleController.BubbleScreenshotListener mBubbleScreenshotListener; /** * Interface to synchronize {@link View} state and the screen. @@ -169,7 +168,6 @@ public class BubbleStackView extends FrameLayout { private ExpandedAnimationController mExpandedAnimationController; private FrameLayout mExpandedViewContainer; - @Nullable private BubbleMenuView mBubbleMenuView; private BubbleFlyoutView mFlyout; /** Runnable that fades out the flyout and then sets it to GONE. */ @@ -516,9 +514,6 @@ public class BubbleStackView extends FrameLayout { mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix)); mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint); }); - - mInflater.inflate(R.layout.bubble_menu_view, this); - mBubbleMenuView = findViewById(R.id.bubble_menu_container); } private void setUpOverflow() { @@ -533,10 +528,6 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.addView(mOverflowBtn, 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mOverflowBtn.setOnClickListener(v -> { - setSelectedBubble(null); - }); - TypedArray ta = mContext.obtainStyledAttributes( new int[]{android.R.attr.colorBackgroundFloating}); int bgColor = ta.getColor(0, Color.WHITE /* default */); @@ -742,13 +733,6 @@ public class BubbleStackView extends FrameLayout { } /** - * Sets the screenshot listener. - */ - public void setBubbleScreenshotListener(BubbleController.BubbleScreenshotListener listener) { - mBubbleScreenshotListener = listener; - } - - /** * Whether the stack of bubbles is expanded or not. */ public boolean isExpanded() { @@ -856,6 +840,10 @@ public class BubbleStackView extends FrameLayout { updateBubbleZOrdersAndDotPosition(false /* animate */); } + void showOverflow() { + setSelectedBubble(null); + } + /** * Changes the currently selected bubble. If the stack is already expanded, the newly selected * bubble will be shown immediately. This does not change the expanded state or change the @@ -942,14 +930,12 @@ public class BubbleStackView extends FrameLayout { public View getTargetView(MotionEvent event) { float x = event.getRawX(); float y = event.getRawY(); - if (mBubbleMenuView.isShowing()) { - if (isIntersecting(mBubbleMenuView.getMenuView(), x, y)) { - return mBubbleMenuView; - } - return null; - } if (mIsExpanded) { if (isIntersecting(mBubbleContainer, x, y)) { + if (BubbleExperimentConfig.allowBubbleOverflow(mContext) + && isIntersecting(mOverflowBtn, x, y)) { + return mOverflowBtn; + } // Could be tapping or dragging a bubble while expanded for (int i = 0; i < getBubbleCount(); i++) { BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i); @@ -1164,7 +1150,6 @@ public class BubbleStackView extends FrameLayout { } return; } - hideBubbleMenu(); mStackAnimationController.cancelStackPositionAnimations(); mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); @@ -1566,10 +1551,6 @@ public class BubbleStackView extends FrameLayout { @Override public void getBoundsOnScreen(Rect outRect) { // If the bubble menu is open, the entire screen should capture touch events. - if (mBubbleMenuView.isShowing()) { - outRect.set(0, 0, getWidth(), getHeight()); - return; - } if (!mIsExpanded) { if (getBubbleCount() > 0) { mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect); @@ -1804,50 +1785,4 @@ public class BubbleStackView extends FrameLayout { } return bubbles; } - - /** - * Show the bubble menu, positioned relative to the stack. - */ - public void showBubbleMenu() { - PointF currentPos = mStackAnimationController.getStackPosition(); - mBubbleMenuView.setVisibility(View.INVISIBLE); - post(() -> { - float yPos = currentPos.y; - float xPos = currentPos.x; - if (mStackAnimationController.isStackOnLeftSide()) { - xPos += mBubbleSize; - } else { - xPos -= mBubbleMenuView.getMenuView().getWidth(); - } - - mBubbleMenuView.show(xPos, yPos); - }); - } - - /** - * Hide the bubble menu. - */ - public void hideBubbleMenu() { - mBubbleMenuView.hide(); - } - - /** - * Determines whether the bubble menu is currently showing. - */ - public boolean isShowingBubbleMenu() { - return mBubbleMenuView.isShowing(); - } - - /** - * Take a screenshot and send it to the specified bubble. - */ - public void sendScreenshotToBubble(Bubble bubble) { - hideBubbleMenu(); - // delay allows the bubble menu to disappear before the screenshot - // done here because we already have a Handler to delay with. - // TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying. - postDelayed(() -> { - mBubbleScreenshotListener.onBubbleScreenshot(bubble); - }, BubbleMenuView.SCREENSHOT_DELAY); - } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index fdeaf1f016c3..645696d0bcac 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -24,6 +24,7 @@ import android.view.View; import android.view.ViewConfiguration; import com.android.systemui.Dependency; +import com.android.systemui.R; /** * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing, @@ -57,14 +58,12 @@ class BubbleTouchHandler implements View.OnTouchListener { private final PointF mViewPositionOnTouchDown = new PointF(); private final BubbleStackView mStack; private final BubbleData mBubbleData; - private final Context mContext; private BubbleController mController = Dependency.get(BubbleController.class); private boolean mMovedEnough; private int mTouchSlopSquared; private VelocityTracker mVelocityTracker; - private Runnable mShowBubbleMenuRunnable; /** View that was initially touched, when we received the first ACTION_DOWN event. */ private View mTouchedView; @@ -77,7 +76,6 @@ class BubbleTouchHandler implements View.OnTouchListener { mTouchSlopSquared = touchSlop * touchSlop; mBubbleData = bubbleData; mStack = stackView; - mContext = context; } @Override @@ -94,24 +92,19 @@ class BubbleTouchHandler implements View.OnTouchListener { // anything, collapse the stack. if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) { mBubbleData.setExpanded(false); - mStack.hideBubbleMenu(); resetForNextGesture(); return false; } - if (mTouchedView instanceof BubbleMenuView) { - mStack.hideBubbleMenu(); - resetForNextGesture(); - mStack.sendScreenshotToBubble(mBubbleData.getSelectedBubble()); - return false; - } - if (!(mTouchedView instanceof BadgedImageView) && !(mTouchedView instanceof BubbleStackView) && !(mTouchedView instanceof BubbleFlyoutView)) { + + if (mTouchedView.getId() == R.id.bubble_overflow_button) { + mStack.showOverflow(); + } // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge // of expanded view). - mStack.hideBubbleMenu(); resetForNextGesture(); return false; } @@ -134,12 +127,6 @@ class BubbleTouchHandler implements View.OnTouchListener { if (isStack) { mViewPositionOnTouchDown.set(mStack.getStackPosition()); mStack.onDragStart(); - if (!mStack.isShowingBubbleMenu() && !mStack.isExpanded() - && BubbleExperimentConfig.allowBubbleScreenshotMenu(mContext)) { - mShowBubbleMenuRunnable = mStack::showBubbleMenu; - mStack.postDelayed(mShowBubbleMenuRunnable, - ViewConfiguration.getLongPressTimeout()); - } } else if (isFlyout) { mStack.onFlyoutDragStart(); } else { @@ -150,10 +137,6 @@ class BubbleTouchHandler implements View.OnTouchListener { break; case MotionEvent.ACTION_MOVE: - // block all further touch inputs once the menu is open - if (mStack.isShowingBubbleMenu()) { - return true; - } trackMovement(event); final float deltaX = rawX - mTouchDown.x; final float deltaY = rawY - mTouchDown.y; @@ -163,7 +146,6 @@ class BubbleTouchHandler implements View.OnTouchListener { } if (mMovedEnough) { - mStack.removeCallbacks(mShowBubbleMenuRunnable); if (isStack) { mStack.onDragged(viewX, viewY); } else if (isFlyout) { @@ -194,12 +176,6 @@ class BubbleTouchHandler implements View.OnTouchListener { break; case MotionEvent.ACTION_UP: - if (mStack.isShowingBubbleMenu()) { - resetForNextGesture(); - return true; - } else { - mStack.removeCallbacks(mShowBubbleMenuRunnable); - } trackMovement(event); mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000); final float velX = mVelocityTracker.getXVelocity(); @@ -222,9 +198,14 @@ class BubbleTouchHandler implements View.OnTouchListener { if (isStack) { mController.dismissStack(BubbleController.DISMISS_USER_GESTURE); } else { - mController.removeBubble( - individualBubbleKey, - BubbleController.DISMISS_USER_GESTURE); + final Bubble bubble = + mBubbleData.getBubbleWithKey(individualBubbleKey); + // bubble can be null if the user is in the middle of + // dismissing the bubble, but the app also sent a cancel + if (bubble != null) { + mController.removeBubble(bubble.getEntry(), + BubbleController.DISMISS_USER_GESTURE); + } } }); } else if (isFlyout) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index aa549dc23f9b..607b5ef9b2c2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -388,8 +388,11 @@ public class ExpandedAnimationController mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); // Includes overflow button. - float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) - - (mBubblesMaxRendered + 1) * mBubbleSizePx; + // TODO(b/148675523) this is a temporary work around; change back once we have proper fix. +// float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) +// - (mBubblesMaxRendered + 1) * mBubbleSizePx; + float totalGapWidth = getAvailableScreenWidth(true /* includeStableInsets */) + - (mExpandedViewPadding * 2) - (mBubblesMaxRendered + 1) * mBubbleSizePx; mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; // Ensure that all child views are at 1x scale, and visible, in case they were animating diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt index e6cdf50580d8..49a16d892ef4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -18,4 +18,8 @@ package com.android.systemui.controls import android.service.controls.Control -data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file +data class ControlStatus( + val control: Control, + var favorite: Boolean, + val removed: Boolean = false +)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt index 265ddd8043b6..588ef5c4e68f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt @@ -22,7 +22,7 @@ import com.android.settingslib.applications.DefaultAppInfo class ControlsServiceInfo( context: Context, - serviceInfo: ServiceInfo + val serviceInfo: ServiceInfo ) : DefaultAppInfo( context, context.packageManager, diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt new file mode 100644 index 000000000000..4f39f2255a75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls + +import android.os.UserHandle + +interface UserAwareController { + + fun changeUser(newUser: UserHandle) {} + val currentUserId: Int +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt index 6b7fc4b7e827..12c3ce9c69ee 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt @@ -19,8 +19,9 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction +import com.android.systemui.controls.UserAwareController -interface ControlsBindingController { +interface ControlsBindingController : UserAwareController { fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) fun bindServices(components: List<ComponentName>) fun subscribe(controls: List<ControlInfo>) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 2db2cf1af191..0a2a9255c3ea 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -19,6 +19,7 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.content.Context import android.os.IBinder +import android.os.UserHandle import android.service.controls.Control import android.service.controls.IControlsActionCallback import android.service.controls.IControlsLoadCallback @@ -50,12 +51,17 @@ open class ControlsBindingControllerImpl @Inject constructor( private val refreshing = AtomicBoolean(false) + private var currentUser = context.user + + override val currentUserId: Int + get() = currentUser.identifier + @GuardedBy("componentMap") private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> = ArrayMap<IBinder, ControlsProviderLifecycleManager>() @GuardedBy("componentMap") - private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> = - ArrayMap<ComponentName, ControlsProviderLifecycleManager>() + private val componentMap: MutableMap<Key, ControlsProviderLifecycleManager> = + ArrayMap<Key, ControlsProviderLifecycleManager>() private val loadCallbackService = object : IControlsLoadCallback.Stub() { override fun accept(token: IBinder, controls: MutableList<Control>) { @@ -103,6 +109,7 @@ open class ControlsBindingControllerImpl @Inject constructor( loadCallbackService, actionCallbackService, subscriberService, + currentUser, component ) } @@ -110,7 +117,7 @@ open class ControlsBindingControllerImpl @Inject constructor( private fun retrieveLifecycleManager(component: ComponentName): ControlsProviderLifecycleManager { synchronized(componentMap) { - val provider = componentMap.getOrPut(component) { + val provider = componentMap.getOrPut(Key(component, currentUser)) { createProviderManager(component) } tokenMap.putIfAbsent(provider.token, provider) @@ -137,7 +144,7 @@ open class ControlsBindingControllerImpl @Inject constructor( val providersWithFavorites = controlsByComponentName.keys synchronized(componentMap) { componentMap.forEach { - if (it.key !in providersWithFavorites) { + if (it.key.component !in providersWithFavorites) { backgroundExecutor.execute { it.value.unbindService() } } } @@ -163,8 +170,38 @@ open class ControlsBindingControllerImpl @Inject constructor( override fun bindServices(components: List<ComponentName>) { components.forEach { val provider = retrieveLifecycleManager(it) - backgroundExecutor.execute { provider.bindPermanently() } + backgroundExecutor.execute { provider.bindService() } + } + } + + override fun changeUser(newUser: UserHandle) { + if (newUser == currentUser) return + synchronized(componentMap) { + unbindAllProvidersLocked() // unbind all providers from the old user } + refreshing.set(false) + currentUser = newUser + } + + private fun unbindAllProvidersLocked() { + componentMap.values.forEach { + if (it.user == currentUser) { + it.unbindService() + } + } + } + + override fun toString(): String { + return StringBuilder(" ControlsBindingController:\n").apply { + append(" refreshing=${refreshing.get()}\n") + append(" currentUser=$currentUser\n") + append(" Providers:\n") + synchronized(componentMap) { + componentMap.values.forEach { + append(" $it\n") + } + } + }.toString() } private abstract inner class CallbackRunnable(val token: IBinder) : Runnable { @@ -183,6 +220,10 @@ open class ControlsBindingControllerImpl @Inject constructor( Log.e(TAG, "No provider found for token:$token") return } + if (provider.user != currentUser) { + Log.e(TAG, "User ${provider.user} is not current user") + return + } synchronized(componentMap) { if (token !in tokenMap.keys) { Log.e(TAG, "Provider for token:$token does not exist anymore") @@ -192,7 +233,7 @@ open class ControlsBindingControllerImpl @Inject constructor( provider.lastLoadCallback?.invoke(list) ?: run { Log.w(TAG, "Null callback") } - provider.maybeUnbindAndRemoveCallback() + provider.unbindService() } } @@ -204,6 +245,10 @@ open class ControlsBindingControllerImpl @Inject constructor( if (!refreshing.get()) { Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}") } + if (provider?.user != currentUser) { + Log.e(TAG, "User ${provider?.user} is not current user") + return + } provider?.let { lazyController.get().refreshStatus(it.componentName, control) } @@ -229,7 +274,7 @@ open class ControlsBindingControllerImpl @Inject constructor( ) : CallbackRunnable(token) { override fun run() { provider?.let { - Log.i(TAG, "onComplete receive from '${provider?.componentName}'") + Log.i(TAG, "onComplete receive from '${provider.componentName}'") } } } @@ -240,7 +285,7 @@ open class ControlsBindingControllerImpl @Inject constructor( ) : CallbackRunnable(token) { override fun run() { provider?.let { - Log.e(TAG, "onError receive from '${provider?.componentName}': $error") + Log.e(TAG, "onError receive from '${provider.componentName}': $error") } } } @@ -251,9 +296,15 @@ open class ControlsBindingControllerImpl @Inject constructor( @ControlAction.ResponseResult val response: Int ) : CallbackRunnable(token) { override fun run() { + if (provider?.user != currentUser) { + Log.e(TAG, "User ${provider?.user} is not current user") + return + } provider?.let { lazyController.get().onActionResponse(it.componentName, controlId, response) } } } } + +private data class Key(val component: ComponentName, val user: UserHandle)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index e098faa00d03..fce504120b62 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -20,14 +20,24 @@ import android.content.ComponentName import android.service.controls.Control import android.service.controls.actions.ControlAction import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.UserAwareController -interface ControlsController { +interface ControlsController : UserAwareController { val available: Boolean fun getFavoriteControls(): List<ControlInfo> - fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit) + fun loadForComponent( + componentName: ComponentName, + callback: (List<ControlStatus>, List<String>) -> Unit + ) + fun subscribeToFavorites() fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) + fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>) + + fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> + fun countFavoritesForComponent(componentName: ComponentName): Int + fun unsubscribe() fun action(controlInfo: ControlInfo, action: ControlAction) fun refreshStatus(componentName: ComponentName, control: Control) 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 d5b5b5f0442e..e611197b78ae 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -17,26 +17,36 @@ package com.android.systemui.controls.controller import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.ContentResolver import android.content.Context import android.content.Intent +import android.content.IntentFilter +import android.database.ContentObserver +import android.net.Uri import android.os.Environment +import android.os.UserHandle import android.provider.Settings import android.service.controls.Control import android.service.controls.actions.ControlAction import android.util.ArrayMap import android.util.Log import com.android.internal.annotations.GuardedBy +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.DumpController import com.android.systemui.Dumpable +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.management.ControlsFavoritingActivity +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor import java.io.PrintWriter import java.util.Optional +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -46,74 +56,178 @@ class ControlsControllerImpl @Inject constructor ( @Background private val executor: DelayableExecutor, private val uiController: ControlsUiController, private val bindingController: ControlsBindingController, - private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, + private val listingController: ControlsListingController, + private val broadcastDispatcher: BroadcastDispatcher, + optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpController: DumpController ) : Dumpable, ControlsController { companion object { private const val TAG = "ControlsControllerImpl" - const val CONTROLS_AVAILABLE = "systemui.controls_available" + internal const val CONTROLS_AVAILABLE = "systemui.controls_available" + internal val URI = Settings.Secure.getUriFor(CONTROLS_AVAILABLE) + private const val USER_CHANGE_RETRY_DELAY = 500L // ms } - override val available = Settings.Secure.getInt( - context.contentResolver, CONTROLS_AVAILABLE, 0) != 0 - val persistenceWrapper = optionalWrapper.orElseGet { + // Map of map: ComponentName -> (String -> ControlInfo). + // + @GuardedBy("currentFavorites") + private val currentFavorites = ArrayMap<ComponentName, MutableList<ControlInfo>>() + .withDefault { mutableListOf() } + + private var userChanging: Boolean = true + + private val contentResolver: ContentResolver + get() = context.contentResolver + override var available = Settings.Secure.getInt(contentResolver, CONTROLS_AVAILABLE, 0) != 0 + private set + + private var currentUser = context.user + override val currentUserId + get() = currentUser.identifier + + private val persistenceWrapper = optionalWrapper.orElseGet { ControlsFavoritePersistenceWrapper( Environment.buildPath( - context.filesDir, - ControlsFavoritePersistenceWrapper.FILE_NAME), + context.filesDir, + ControlsFavoritePersistenceWrapper.FILE_NAME + ), executor ) } - // Map of map: ComponentName -> (String -> ControlInfo) - @GuardedBy("currentFavorites") - private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + private fun setValuesForUser(newUser: UserHandle) { + Log.d(TAG, "Changing to user: $newUser") + currentUser = newUser + val userContext = context.createContextAsUser(currentUser, 0) + val fileName = Environment.buildPath( + userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME) + persistenceWrapper.changeFile(fileName) + available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE, + /* default */ 0, newUser.identifier) != 0 + synchronized(currentFavorites) { + currentFavorites.clear() + } + if (available) { + loadFavorites() + } + bindingController.changeUser(newUser) + listingController.changeUser(newUser) + userChanging = false + } + + private val userSwitchReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_USER_SWITCHED) { + userChanging = true + val newUser = + UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId)) + if (currentUser == newUser) { + userChanging = false + return + } + setValuesForUser(newUser) + } + } + } + + @VisibleForTesting + internal val settingObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean, uri: Uri, userId: Int) { + // Do not listen to changes in the middle of user change, those will be read by the + // user-switch receiver. + if (userChanging || userId != currentUserId) { + return + } + available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE, + /* default */ 0, currentUserId) != 0 + synchronized(currentFavorites) { + currentFavorites.clear() + } + if (available) { + loadFavorites() + } + } + } init { + dumpController.registerDumpable(this) if (available) { - dumpController.registerDumpable(this) loadFavorites() } + userChanging = false + broadcastDispatcher.registerReceiver( + userSwitchReceiver, + IntentFilter(Intent.ACTION_USER_SWITCHED), + executor, + UserHandle.ALL + ) + contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL) + } + + private fun confirmAvailability(): Boolean { + if (userChanging) { + Log.w(TAG, "Controls not available while user is changing") + return false + } + if (!available) { + Log.d(TAG, "Controls not available") + return false + } + return true } private fun loadFavorites() { val infos = persistenceWrapper.readFavorites() synchronized(currentFavorites) { infos.forEach { - currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() }) - .put(it.controlId, it) + currentFavorites.getOrPut(it.component, { mutableListOf() }).add(it) } } } override fun loadForComponent( componentName: ComponentName, - callback: (List<ControlStatus>) -> Unit + callback: (List<ControlStatus>, List<String>) -> Unit ) { - if (!available) { - Log.d(TAG, "Controls not available") + if (!confirmAvailability()) { + if (userChanging) { + // Try again later, userChanging should not last forever. If so, we have bigger + // problems + executor.executeDelayed( + { loadForComponent(componentName, callback) }, + USER_CHANGE_RETRY_DELAY, + TimeUnit.MILLISECONDS + ) + } else { + callback(emptyList(), emptyList()) + } return } bindingController.bindAndLoad(componentName) { synchronized(currentFavorites) { - val favoritesForComponentKeys: Set<String> = - currentFavorites.get(componentName)?.keys ?: emptySet() - val changed = updateFavoritesLocked(componentName, it) + val favoritesForComponentKeys: List<String> = + currentFavorites.getValue(componentName).map { it.controlId } + val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } - val removed = findRemovedLocked(favoritesForComponentKeys, it) - callback(removed.map { currentFavorites.getValue(componentName).getValue(it) } - .map(::createRemovedStatus) + - it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }) + val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it) + val controlsWithFavorite = + it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) } + callback( + currentFavorites.getValue(componentName) + .filter { it.controlId in removed } + .map(::createRemovedStatus) + controlsWithFavorite, + favoritesForComponentKeys + ) } } } private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus { val intent = Intent(context, ControlsFavoritingActivity::class.java).apply { - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component) + putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP } val pendingIntent = PendingIntent.getActivity(context, @@ -134,17 +248,24 @@ class ControlsControllerImpl @Inject constructor ( } @GuardedBy("currentFavorites") - private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean { - val favorites = currentFavorites.get(componentName) ?: mutableMapOf() - val favoriteKeys = favorites.keys + private fun updateFavoritesLocked( + componentName: ComponentName, + list: List<Control>, + favoriteKeys: List<String> + ): Boolean { + val favorites = currentFavorites.get(componentName) ?: mutableListOf() if (favoriteKeys.isEmpty()) return false // early return var changed = false - list.forEach { - if (it.controlId in favoriteKeys) { - val value = favorites.getValue(it.controlId) - if (value.controlTitle != it.title || value.deviceType != it.deviceType) { - favorites[it.controlId] = value.copy(controlTitle = it.title, - deviceType = it.deviceType) + list.forEach { control -> + if (control.controlId in favoriteKeys) { + val index = favorites.indexOfFirst { it.controlId == control.controlId } + val value = favorites[index] + if (value.controlTitle != control.title || + value.deviceType != control.deviceType) { + favorites[index] = value.copy( + controlTitle = control.title, + deviceType = control.deviceType + ) changed = true } } @@ -154,54 +275,42 @@ class ControlsControllerImpl @Inject constructor ( @GuardedBy("currentFavorites") private fun favoritesAsListLocked(): List<ControlInfo> { - return currentFavorites.flatMap { it.value.values } + return currentFavorites.flatMap { it.value } } override fun subscribeToFavorites() { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return // Make a copy of the favorites list val favorites = synchronized(currentFavorites) { - currentFavorites.flatMap { it.value.values.toList() } + currentFavorites.flatMap { it.value } } bindingController.subscribe(favorites) } override fun unsubscribe() { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return bindingController.unsubscribe() } override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return var changed = false val listOfControls = synchronized(currentFavorites) { if (state) { if (controlInfo.component !in currentFavorites) { - currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>()) + currentFavorites.put(controlInfo.component, mutableListOf()) changed = true } val controlsForComponent = currentFavorites.getValue(controlInfo.component) - if (controlInfo.controlId !in controlsForComponent) { - controlsForComponent.put(controlInfo.controlId, controlInfo) + if (controlsForComponent.firstOrNull { + it.controlId == controlInfo.controlId + } == null) { + controlsForComponent.add(controlInfo) changed = true - } else { - if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) { - controlsForComponent.put(controlInfo.controlId, controlInfo) - changed = true - } } } else { changed = currentFavorites.get(controlInfo.component) - ?.remove(controlInfo.controlId) != null + ?.remove(controlInfo) != null } favoritesAsListLocked() } @@ -210,14 +319,33 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun replaceFavoritesForComponent( + componentName: ComponentName, + favorites: List<ControlInfo> + ) { + if (!confirmAvailability()) return + val filtered = favorites.filter { it.component == componentName } + val listOfControls = synchronized(currentFavorites) { + currentFavorites.put(componentName, filtered.toMutableList()) + favoritesAsListLocked() + } + persistenceWrapper.storeFavorites(listOfControls) + } + override fun refreshStatus(componentName: ComponentName, control: Control) { - if (!available) { + if (!confirmAvailability()) { Log.d(TAG, "Controls not available") return } executor.execute { synchronized(currentFavorites) { - val changed = updateFavoritesLocked(componentName, listOf(control)) + val favoriteKeysForComponent = + currentFavorites.get(componentName)?.map { it.controlId } ?: emptyList() + val changed = updateFavoritesLocked( + componentName, + listOf(control), + favoriteKeysForComponent + ) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } @@ -227,28 +355,24 @@ class ControlsControllerImpl @Inject constructor ( } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { - if (!available) { - Log.d(TAG, "Controls not available") - return - } + if (!confirmAvailability()) return uiController.onActionResponse(componentName, controlId, response) } override fun getFavoriteControls(): List<ControlInfo> { - if (!available) { - Log.d(TAG, "Controls not available") - return emptyList() - } + if (!confirmAvailability()) return emptyList() synchronized(currentFavorites) { return favoritesAsListLocked() } } override fun action(controlInfo: ControlInfo, action: ControlAction) { + if (!confirmAvailability()) return bindingController.action(controlInfo, action) } override fun clearFavorites() { + if (!confirmAvailability()) return val changed = synchronized(currentFavorites) { currentFavorites.isNotEmpty().also { currentFavorites.clear() @@ -259,15 +383,29 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun countFavoritesForComponent(componentName: ComponentName): Int { + return synchronized(currentFavorites) { + currentFavorites.get(componentName)?.size ?: 0 + } + } + + override fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> { + return synchronized(currentFavorites) { + currentFavorites.get(componentName) ?: emptyList() + } + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") + pw.println(" Available: $available") + pw.println(" Changing users: $userChanging") + pw.println(" Current user: ${currentUser.identifier}") pw.println(" Favorites:") synchronized(currentFavorites) { - currentFavorites.forEach { - it.value.forEach { - pw.println(" ${it.value}") - } + favoritesAsListLocked().forEach { + pw.println(" ${ it }") } } + pw.println(bindingController.toString()) } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt index 6f2d71fd0f59..7d1df14bbf1b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt @@ -16,7 +16,6 @@ package com.android.systemui.controls.controller -import android.app.ActivityManager import android.content.ComponentName import android.util.AtomicFile import android.util.Log @@ -32,8 +31,8 @@ import java.io.FileNotFoundException import java.io.IOException class ControlsFavoritePersistenceWrapper( - val file: File, - val executor: DelayableExecutor + private var file: File, + private var executor: DelayableExecutor ) { companion object { @@ -47,11 +46,13 @@ class ControlsFavoritePersistenceWrapper( private const val TAG_TYPE = "type" } - val currentUser: Int - get() = ActivityManager.getCurrentUser() + fun changeFile(fileName: File) { + file = fileName + } fun storeFavorites(list: List<ControlInfo>) { executor.execute { + Log.d(TAG, "Saving data to file: $file") val atomicFile = AtomicFile(file) val writer = try { atomicFile.startWrite() @@ -98,6 +99,7 @@ class ControlsFavoritePersistenceWrapper( return emptyList() } try { + Log.d(TAG, "Reading data from file: $file") val parser = Xml.newPullParser() parser.setInput(reader, null) return parseXml(parser) @@ -111,7 +113,7 @@ class ControlsFavoritePersistenceWrapper( } private fun parseXml(parser: XmlPullParser): List<ControlInfo> { - var type: Int = 0 + var type = 0 val infos = mutableListOf<ControlInfo>() while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -123,9 +125,9 @@ class ControlsFavoritePersistenceWrapper( parser.getAttributeValue(null, TAG_COMPONENT)) val id = parser.getAttributeValue(null, TAG_ID) val title = parser.getAttributeValue(null, TAG_TITLE) - val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt() - if (component != null && id != null && title != null && type != null) { - infos.add(ControlInfo(component, id, title, type)) + val deviceType = parser.getAttributeValue(null, TAG_TYPE)?.toInt() + if (component != null && id != null && title != null && deviceType != null) { + infos.add(ControlInfo(component, id, title, deviceType)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 99aa3601ba30..b4bd82c84e1a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -24,6 +24,7 @@ import android.os.Binder import android.os.Bundle import android.os.IBinder import android.os.RemoteException +import android.os.UserHandle import android.service.controls.Control import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE import android.service.controls.ControlsProviderService.CALLBACK_TOKEN @@ -46,6 +47,7 @@ class ControlsProviderLifecycleManager( private val loadCallbackService: IControlsLoadCallback.Stub, private val actionCallbackService: IControlsActionCallback.Stub, private val subscriberService: IControlsSubscriber.Stub, + val user: UserHandle, val componentName: ComponentName ) : IBinder.DeathRecipient { @@ -54,9 +56,7 @@ class ControlsProviderLifecycleManager( val token: IBinder = Binder() @GuardedBy("subscriptions") private val subscriptions = mutableListOf<IControlsSubscription>() - private var unbindImmediate = false private var requiresBound = false - private var isBound = false @GuardedBy("queuedMessages") private val queuedMessages: MutableSet<Message> = ArraySet() private var wrapper: ServiceWrapper? = null @@ -96,30 +96,22 @@ class ControlsProviderLifecycleManager( } bindTryCount++ try { - isBound = context.bindService(intent, serviceConnection, BIND_FLAGS) + context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user) } catch (e: SecurityException) { Log.e(TAG, "Failed to bind to service", e) - isBound = false } } else { if (DEBUG) { Log.d(TAG, "Unbinding service $intent") } bindTryCount = 0 - wrapper = null - if (isBound) { + wrapper?.run { context.unbindService(serviceConnection) - isBound = false } + wrapper = null } } - fun bindPermanently() { - unbindImmediate = false - unqueueMessage(Message.Unbind) - bindService(true) - } - private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { if (DEBUG) Log.d(TAG, "onServiceConnected $name") @@ -133,7 +125,7 @@ class ControlsProviderLifecycleManager( override fun onServiceDisconnected(name: ComponentName?) { if (DEBUG) Log.d(TAG, "onServiceDisconnected $name") - isBound = false + wrapper = null bindService(false) } } @@ -152,7 +144,9 @@ class ControlsProviderLifecycleManager( load() } queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run { - subscribe(this) + if (this.isNotEmpty()) { + subscribe(this) + } } queue.filter { it is Message.Action }.forEach { val msg = it as Message.Action @@ -193,6 +187,15 @@ class ControlsProviderLifecycleManager( } } + private fun invokeOrQueue(f: () -> Unit, msg: Message) { + wrapper?.run { + f() + } ?: run { + queueMessage(msg) + bindService(true) + } + } + fun maybeBindAndLoad(callback: LoadCallback) { unqueueMessage(Message.Unbind) lastLoadCallback = callback @@ -201,22 +204,12 @@ class ControlsProviderLifecycleManager( Log.d(TAG, "Timeout waiting onLoad for $componentName") loadCallbackService.accept(token, emptyList()) }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS) - if (isBound) { - load() - } else { - queueMessage(Message.Load) - unbindImmediate = true - bindService(true) - } + + invokeOrQueue(::load, Message.Load) } fun maybeBindAndSubscribe(controlIds: List<String>) { - if (isBound) { - subscribe(controlIds) - } else { - queueMessage(Message.Subscribe(controlIds)) - bindService(true) - } + invokeOrQueue({ subscribe(controlIds) }, Message.Subscribe(controlIds)) } private fun subscribe(controlIds: List<String>) { @@ -230,12 +223,7 @@ class ControlsProviderLifecycleManager( } fun maybeBindAndSendAction(controlId: String, action: ControlAction) { - if (isBound) { - action(controlId, action) - } else { - queueMessage(Message.Action(controlId, action)) - bindService(true) - } + invokeOrQueue({ action(controlId, action) }, Message.Action(controlId, action)) } private fun action(controlId: String, action: ControlAction) { @@ -272,18 +260,25 @@ class ControlsProviderLifecycleManager( } } - fun maybeUnbindAndRemoveCallback() { + fun bindService() { + unqueueMessage(Message.Unbind) + bindService(true) + } + + fun unbindService() { lastLoadCallback = null onLoadCanceller?.run() onLoadCanceller = null - if (unbindImmediate) { - bindService(false) - } + + bindService(false) } - fun unbindService() { - unbindImmediate = true - maybeUnbindAndRemoveCallback() + override fun toString(): String { + return StringBuilder("ControlsProviderLifecycleManager(").apply { + append("component=$componentName") + append(", user=$user") + append(")") + }.toString() } sealed class Message { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt index d62bb4def3aa..89caaceebb5c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -17,15 +17,19 @@ package com.android.systemui.controls.management import android.content.ComponentName +import android.content.res.Resources import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView +import com.android.settingslib.applications.DefaultAppInfo import com.android.settingslib.widget.CandidateInfo import com.android.systemui.R +import java.text.Collator import java.util.concurrent.Executor /** @@ -41,20 +45,27 @@ import java.util.concurrent.Executor * @param onAppSelected a callback to indicate that an app has been selected in the list. */ class AppAdapter( + backgroundExecutor: Executor, uiExecutor: Executor, lifecycle: Lifecycle, controlsListingController: ControlsListingController, private val layoutInflater: LayoutInflater, - private val onAppSelected: (ComponentName?) -> Unit = {} + private val onAppSelected: (ComponentName?) -> Unit = {}, + private val favoritesRenderer: FavoritesRenderer, + private val resources: Resources ) : RecyclerView.Adapter<AppAdapter.Holder>() { private var listOfServices = emptyList<CandidateInfo>() private val callback = object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(list: List<CandidateInfo>) { - uiExecutor.execute { - listOfServices = list - notifyDataSetChanged() + backgroundExecutor.execute { + val collator = Collator.getInstance(resources.getConfiguration().locale) + val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) { + it.loadLabel() + } + listOfServices = list.sortedWith(localeComparator) + uiExecutor.execute(::notifyDataSetChanged) } } } @@ -64,7 +75,8 @@ class AppAdapter( } override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { - return Holder(layoutInflater.inflate(R.layout.app_item, parent, false)) + return Holder(layoutInflater.inflate(R.layout.controls_app_item, parent, false), + favoritesRenderer) } override fun getItemCount() = listOfServices.size @@ -79,9 +91,10 @@ class AppAdapter( /** * Holder for binding views in the [RecyclerView]- */ - class Holder(view: View) : RecyclerView.ViewHolder(view) { + class Holder(view: View, val favRenderer: FavoritesRenderer) : RecyclerView.ViewHolder(view) { private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon) private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title) + private val favorites: TextView = itemView.requireViewById(R.id.favorites) /** * Bind data to the view @@ -90,6 +103,19 @@ class AppAdapter( fun bindData(data: CandidateInfo) { icon.setImageDrawable(data.loadIcon()) title.text = data.loadLabel() + favorites.text = favRenderer.renderFavoritesForComponent( + (data as DefaultAppInfo).componentName) } } +} + +class FavoritesRenderer( + private val resources: Resources, + private val favoriteFunction: (ComponentName) -> Int +) { + + fun renderFavoritesForComponent(component: ComponentName): String { + val qty = favoriteFunction(component) + return resources.getQuantityString(R.plurals.controls_number_of_favorites, qty, qty) + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index e6d3c26ea7b8..d3cabe67790e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -16,74 +16,177 @@ package com.android.systemui.controls.management +import android.graphics.Rect +import android.graphics.drawable.Icon +import android.service.controls.DeviceTypes import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CheckBox +import android.widget.ImageView import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R -import com.android.systemui.controls.ControlStatus -import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.ui.RenderInfo + +private typealias ModelFavoriteChanger = (String, Boolean) -> Unit /** * Adapter for binding [Control] information to views. * + * The model for this adapter is provided by a [FavoriteModel] that is set using + * [changeFavoritesModel]. This allows for updating the model if there's a reload. + * * @param layoutInflater an inflater for the views in the containing [RecyclerView] - * @param favoriteCallback a callback to be called when the favorite status of a [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite - * status. + * @param onlyFavorites set to true to only display favorites instead of all controls */ class ControlAdapter( private val layoutInflater: LayoutInflater, - private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit -) : RecyclerView.Adapter<ControlAdapter.Holder>() { + private val onlyFavorites: Boolean = false +) : RecyclerView.Adapter<Holder>() { + + companion object { + private const val TYPE_ZONE = 0 + private const val TYPE_CONTROL = 1 + } + + val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (getItemViewType(position) == TYPE_ZONE) 2 else 1 + } + } + + var modelList: List<ElementWrapper> = emptyList() + private var favoritesModel: FavoriteModel? = null - var listOfControls = emptyList<ControlStatus>() + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + return when (viewType) { + TYPE_CONTROL -> { + ControlHolder( + layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { + layoutParams.apply { + width = ViewGroup.LayoutParams.MATCH_PARENT + } + elevation = 15f + }, + { id, favorite -> + favoritesModel?.changeFavoriteStatus(id, favorite) + }) + } + TYPE_ZONE -> { + ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false)) + } + else -> throw IllegalStateException("Wrong viewType: $viewType") + } + } - override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { - return Holder(layoutInflater.inflate(R.layout.control_item, parent, false)) + fun changeFavoritesModel(favoritesModel: FavoriteModel) { + this.favoritesModel = favoritesModel + if (onlyFavorites) { + modelList = favoritesModel.favorites + } else { + modelList = favoritesModel.all + } + notifyDataSetChanged() } - override fun getItemCount() = listOfControls.size + override fun getItemCount() = modelList.size override fun onBindViewHolder(holder: Holder, index: Int) { - holder.bindData(listOfControls[index], favoriteCallback) + holder.bindData(modelList[index]) + } + + override fun getItemViewType(position: Int): Int { + return when (modelList[position]) { + is ZoneNameWrapper -> TYPE_ZONE + is ControlWrapper -> TYPE_CONTROL + } } +} + +/** + * Holder for binding views in the [RecyclerView]- + * @param view the [View] for this [Holder] + */ +sealed class Holder(view: View) : RecyclerView.ViewHolder(view) { /** - * Holder for binding views in the [RecyclerView]- + * Bind the data from the model into the view */ - class Holder(view: View) : RecyclerView.ViewHolder(view) { - private val title: TextView = itemView.requireViewById(R.id.title) - private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) - private val favorite: CheckBox = itemView.requireViewById(R.id.favorite) - - /** - * Bind data to the view - * @param data information about the [Control] - * @param callback a callback to be called when the favorite status of the [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite status. - */ - fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) { - title.text = data.control.title - subtitle.text = data.control.subtitle - favorite.isChecked = data.favorite - favorite.setOnClickListener { - val infoBuilder = ControlInfo.Builder().apply { - controlId = data.control.controlId - controlTitle = data.control.title - deviceType = data.control.deviceType - } - callback(infoBuilder, favorite.isChecked) - } + abstract fun bindData(wrapper: ElementWrapper) +} + +/** + * Holder for using with [ZoneNameWrapper] to display names of zones. + */ +private class ZoneHolder(view: View) : Holder(view) { + private val zone: TextView = itemView as TextView + + override fun bindData(wrapper: ElementWrapper) { + wrapper as ZoneNameWrapper + zone.text = wrapper.zoneName + } +} + +/** + * Holder for using with [ControlWrapper] to display names of zones. + * @param favoriteCallback this callback will be called whenever the favorite state of the + * [Control] this view represents changes. + */ +private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) { + private val icon: ImageView = itemView.requireViewById(R.id.icon) + private val title: TextView = itemView.requireViewById(R.id.title) + private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) + private val removed: TextView = itemView.requireViewById(R.id.status) + private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { + visibility = View.VISIBLE + } + + override fun bindData(wrapper: ElementWrapper) { + wrapper as ControlWrapper + val data = wrapper.controlStatus + val renderInfo = getRenderInfo(data.control.deviceType) + title.text = data.control.title + subtitle.text = data.control.subtitle + favorite.isChecked = data.favorite + removed.text = if (data.removed) "Removed" else "" + favorite.setOnClickListener { + favoriteCallback(data.control.controlId, favorite.isChecked) } + applyRenderInfo(renderInfo) } - fun setItems(list: List<ControlStatus>) { - listOfControls = list - notifyDataSetChanged() + private fun getRenderInfo( + @DeviceTypes.DeviceType deviceType: Int + ): RenderInfo { + return RenderInfo.lookup(deviceType, true) + } + + private fun applyRenderInfo(ri: RenderInfo) { + val context = itemView.context + val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + + icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) + icon.setImageTintList(fg) + } +} + +class MarginItemDecorator( + private val topMargin: Int, + private val sideMargins: Int +) : RecyclerView.ItemDecoration() { + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + outRect.apply { + top = topMargin + left = sideMargins + right = sideMargins + } } }
\ No newline at end of file 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 01c4fef67fd4..1e5237148188 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -18,62 +18,148 @@ package com.android.systemui.controls.management import android.app.Activity import android.content.ComponentName +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater -import androidx.recyclerview.widget.LinearLayoutManager +import android.view.ViewStub +import android.widget.Button +import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.CurrentUserTracker import java.util.concurrent.Executor import javax.inject.Inject class ControlsFavoritingActivity @Inject constructor( @Main private val executor: Executor, - private val controller: ControlsControllerImpl + private val controller: ControlsControllerImpl, + broadcastDispatcher: BroadcastDispatcher ) : Activity() { companion object { private const val TAG = "ControlsFavoritingActivity" const val EXTRA_APP = "extra_app_label" - const val EXTRA_COMPONENT = "extra_component" } - private lateinit var recyclerView: RecyclerView - private lateinit var adapter: ControlAdapter + private lateinit var recyclerViewAll: RecyclerView + private lateinit var adapterAll: ControlAdapter + private lateinit var recyclerViewFavorites: RecyclerView + private lateinit var adapterFavorites: ControlAdapter + private var component: ComponentName? = null + + private var currentModel: FavoriteModel? = null + private var itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( + /* dragDirs */ ItemTouchHelper.UP + or ItemTouchHelper.DOWN + or ItemTouchHelper.LEFT + or ItemTouchHelper.RIGHT, + /* swipeDirs */0 + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return currentModel?.onMoveItem( + viewHolder.adapterPosition, target.adapterPosition) != null + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun isItemViewSwipeEnabled() = false + } + + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { + private val startingUser = controller.currentUserId + + override fun onUserSwitched(newUserId: Int) { + if (newUserId != startingUser) { + stopTracking() + finish() + } + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_favorites + inflate() + } val app = intent.getCharSequenceExtra(EXTRA_APP) - val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) + component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) - // If we have no component name, there's not much we can do. - val callback = component?.let { - { infoBuilder: ControlInfo.Builder, status: Boolean -> - infoBuilder.componentName = it - controller.changeFavoriteStatus(infoBuilder.build(), status) - } - } ?: { _, _ -> Unit } + setUpRecyclerViews() - recyclerView = RecyclerView(applicationContext) - adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback) - recyclerView.adapter = adapter - recyclerView.layoutManager = LinearLayoutManager(applicationContext) + requireViewById<TextView>(R.id.title).text = app?.let { it } + ?: resources.getText(R.string.controls_favorite_default_title) + requireViewById<TextView>(R.id.subtitle).text = + resources.getText(R.string.controls_favorite_subtitle) - if (app != null) { - setTitle("Controls for $app") - } else { - setTitle("Controls") + requireViewById<Button>(R.id.done).setOnClickListener { + if (component == null) return@setOnClickListener + val favoritesForStorage = currentModel?.favorites?.map { + with(it.controlStatus.control) { + ControlInfo(component!!, controlId, title, deviceType) + } + } + if (favoritesForStorage != null) { + controller.replaceFavoritesForComponent(component!!, favoritesForStorage) + finishAffinity() + } } - setContentView(recyclerView) component?.let { - controller.loadForComponent(it) { + controller.loadForComponent(it) { allControls, favoriteKeys -> executor.execute { - adapter.setItems(it) + val favoriteModel = FavoriteModel( + allControls, + favoriteKeys, + allAdapter = adapterAll, + favoritesAdapter = adapterFavorites) + adapterAll.changeFavoritesModel(favoriteModel) + adapterFavorites.changeFavoritesModel(favoriteModel) + currentModel = favoriteModel } } } + + currentUserTracker.startTracking() + } + + private fun setUpRecyclerViews() { + val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) + val itemDecorator = MarginItemDecorator(margin, margin) + val layoutInflater = LayoutInflater.from(applicationContext) + + adapterAll = ControlAdapter(layoutInflater) + recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply { + adapter = adapterAll + layoutManager = GridLayoutManager(applicationContext, 2).apply { + spanSizeLookup = adapterAll.spanSizeLookup + } + addItemDecoration(itemDecorator) + } + + adapterFavorites = ControlAdapter(layoutInflater, true) + recyclerViewFavorites = requireViewById<RecyclerView>(R.id.listFavorites).apply { + layoutManager = GridLayoutManager(applicationContext, 2) + adapter = adapterFavorites + addItemDecoration(itemDecorator) + } + ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerViewFavorites) + } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt index 09e0ce9fea8d..34db684022fb 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt @@ -18,14 +18,17 @@ package com.android.systemui.controls.management import android.content.ComponentName import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.controls.UserAwareController import com.android.systemui.statusbar.policy.CallbackController interface ControlsListingController : - CallbackController<ControlsListingController.ControlsListingCallback> { + CallbackController<ControlsListingController.ControlsListingCallback>, + UserAwareController { fun getCurrentServices(): List<CandidateInfo> fun getAppLabel(name: ComponentName): CharSequence? = "" + @FunctionalInterface interface ControlsListingCallback { fun onServicesUpdated(list: List<CandidateInfo>) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 3949c5929a85..882382cc4ade 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -19,6 +19,7 @@ package com.android.systemui.controls.management import android.content.ComponentName import android.content.Context import android.content.pm.ServiceInfo +import android.os.UserHandle import android.service.controls.ControlsProviderService import android.util.Log import com.android.internal.annotations.VisibleForTesting @@ -31,6 +32,16 @@ import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton +private fun createServiceListing(context: Context): ServiceListing { + return ServiceListing.Builder(context).apply { + setIntentAction(ControlsProviderService.SERVICE_CONTROLS) + setPermission("android.permission.BIND_CONTROLS") + setNoun("Controls Provider") + setSetting("controls_providers") + setTag("controls_providers") + }.build() +} + /** * Provides a listing of components to be used as ControlsServiceProvider. * @@ -43,41 +54,55 @@ import javax.inject.Singleton class ControlsListingControllerImpl @VisibleForTesting constructor( private val context: Context, @Background private val backgroundExecutor: Executor, - private val serviceListing: ServiceListing + private val serviceListingBuilder: (Context) -> ServiceListing ) : ControlsListingController { @Inject constructor(context: Context, executor: Executor): this( context, executor, - ServiceListing.Builder(context) - .setIntentAction(ControlsProviderService.SERVICE_CONTROLS) - .setPermission("android.permission.BIND_CONTROLS") - .setNoun("Controls Provider") - .setSetting("controls_providers") - .setTag("controls_providers") - .build() + ::createServiceListing ) + private var serviceListing = serviceListingBuilder(context) + companion object { private const val TAG = "ControlsListingControllerImpl" } private var availableServices = emptyList<ServiceInfo>() - init { - serviceListing.addCallback { - Log.d(TAG, "ServiceConfig reloaded") - availableServices = it.toList() - - backgroundExecutor.execute { - callbacks.forEach { - it.onServicesUpdated(getCurrentServices()) - } + override var currentUserId = context.userId + private set + + private val serviceListingCallback = ServiceListing.Callback { + Log.d(TAG, "ServiceConfig reloaded") + availableServices = it.toList() + + backgroundExecutor.execute { + callbacks.forEach { + it.onServicesUpdated(getCurrentServices()) } } } + init { + serviceListing.addCallback(serviceListingCallback) + } + + override fun changeUser(newUser: UserHandle) { + backgroundExecutor.execute { + callbacks.clear() + availableServices = emptyList() + serviceListing.setListening(false) + serviceListing.removeCallback(serviceListingCallback) + currentUserId = newUser.identifier + val contextForUser = context.createContextAsUser(newUser, 0) + serviceListing = serviceListingBuilder(contextForUser) + serviceListing.addCallback(serviceListingCallback) + } + } + // All operations in background thread private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>() @@ -91,6 +116,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( */ override fun addCallback(listener: ControlsListingController.ControlsListingCallback) { backgroundExecutor.execute { + Log.d(TAG, "Subscribing callback") callbacks.add(listener) if (callbacks.size == 1) { serviceListing.setListening(true) @@ -108,6 +134,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( */ override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) { backgroundExecutor.execute { + Log.d(TAG, "Unsubscribing callback") callbacks.remove(listener) if (callbacks.size == 0) { serviceListing.setListening(false) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 69af516b4ac9..ad4bdefdff3e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -20,9 +20,16 @@ import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewStub +import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.CurrentUserTracker import com.android.systemui.util.LifecycleActivity import java.util.concurrent.Executor import javax.inject.Inject @@ -32,7 +39,10 @@ import javax.inject.Inject */ class ControlsProviderSelectorActivity @Inject constructor( @Main private val executor: Executor, - private val listingController: ControlsListingController + @Background private val backExecutor: Executor, + private val listingController: ControlsListingController, + private val controlsController: ControlsController, + broadcastDispatcher: BroadcastDispatcher ) : LifecycleActivity() { companion object { @@ -40,16 +50,43 @@ class ControlsProviderSelectorActivity @Inject constructor( } private lateinit var recyclerView: RecyclerView + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { + private val startingUser = listingController.currentUserId + + override fun onUserSwitched(newUserId: Int) { + if (newUserId != startingUser) { + stopTracking() + finish() + } + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_apps + inflate() + } - recyclerView = RecyclerView(applicationContext) - recyclerView.adapter = AppAdapter(executor, lifecycle, listingController, - LayoutInflater.from(this), ::launchFavoritingActivity) + recyclerView = requireViewById(R.id.list) + recyclerView.adapter = AppAdapter( + backExecutor, + executor, + lifecycle, + listingController, + LayoutInflater.from(this), + ::launchFavoritingActivity, + FavoritesRenderer(resources, controlsController::countFavoritesForComponent), + resources) recyclerView.layoutManager = LinearLayoutManager(applicationContext) - setContentView(recyclerView) + requireViewById<TextView>(R.id.title).text = + resources.getText(R.string.controls_providers_title) + requireViewById<TextView>(R.id.subtitle).text = + resources.getText(R.string.controls_providers_subtitle) + + currentUserTracker.startTracking() } /** @@ -57,13 +94,22 @@ class ControlsProviderSelectorActivity @Inject constructor( * @param component a component name for a [ControlsProviderService] */ fun launchFavoritingActivity(component: ComponentName?) { - component?.let { - val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply { - putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it)) - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + backExecutor.execute { + component?.let { + val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java) + .apply { + putExtra(ControlsFavoritingActivity.EXTRA_APP, + listingController.getAppLabel(it)) + putExtra(Intent.EXTRA_COMPONENT_NAME, it) + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + } + startActivity(intent) } - startActivity(intent) } } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt new file mode 100644 index 000000000000..6bade0aeb998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.text.TextUtils +import android.util.Log +import com.android.systemui.controls.ControlStatus +import java.util.Collections +import java.util.Comparator + +/** + * Model for keeping track of current favorites and their order. + * + * This model is to be used with two [ControlAdapter] one that shows only favorites in the current + * order and another that shows all controls, separated by zone. When the favorite state of any + * control is modified or when the favorites are reordered, the adapters are notified of the change. + * + * @param listControls list of all the [ControlStatus] to display. This includes controls currently + * marked as favorites as well as those that have been removed (not returned + * from load) + * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those + * that have been removed. + * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites + * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls + */ +class FavoriteModel( + private val listControls: List<ControlStatus>, + listFavoritesIds: List<String>, + private val favoritesAdapter: ControlAdapter, + private val allAdapter: ControlAdapter +) { + + companion object { + private const val TAG = "FavoriteModel" + } + + /** + * List of favorite controls ([ControlWrapper]) in order. + * + * Initially, this list will give a list of wrappers in the order specified by the constructor + * variable `listFavoriteIds`. + * + * As the favorites are added, removed or moved, this list will keep track of those changes. + */ + val favorites: List<ControlWrapper> = listFavoritesIds.map { id -> + ControlWrapper(listControls.first { it.control.controlId == id }) + }.toMutableList() + + /** + * List of all controls by zones. + * + * Lists all the controls with the zone names interleaved as a flat list. After each zone name, + * the controls in that zone are listed. Zones are listed in alphabetical order + */ + val all: List<ElementWrapper> = listControls.groupBy { it.control.zone } + .mapKeys { it.key ?: "" } // map null to empty + .toSortedMap(CharSequenceComparator()) + .flatMap { + val controls = it.value.map { ControlWrapper(it) } + if (!TextUtils.isEmpty(it.key)) { + listOf(ZoneNameWrapper(it.key)) + controls + } else { + controls + } + } + + /** + * Change the favorite status of a [Control]. + * + * This can be invoked from any of the [ControlAdapter]. It will change the status of that + * control and either add it to the list of favorites (at the end) or remove it from it. + * + * Removing the favorite status from a Removed control will make it disappear completely if + * changes are saved. + * + * @param controlId the id of the [Control] to change the status + * @param favorite `true` if and only if it's set to be a favorite. + */ + fun changeFavoriteStatus(controlId: String, favorite: Boolean) { + favorites as MutableList + val index = all.indexOfFirst { + it is ControlWrapper && it.controlStatus.control.controlId == controlId + } + val control = (all[index] as ControlWrapper).controlStatus + if (control.favorite == favorite) { + Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ") + return + } else { + control.favorite = favorite + } + allAdapter.notifyItemChanged(index) + if (favorite) { + favorites.add(all[index] as ControlWrapper) + favoritesAdapter.notifyItemInserted(favorites.size - 1) + } else { + val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId } + favorites.removeAt(i) + favoritesAdapter.notifyItemRemoved(i) + } + } + + /** + * Move items in the model and notify the [favoritesAdapter]. + */ + fun onMoveItem(from: Int, to: Int) { + if (from < to) { + for (i in from until to) { + Collections.swap(favorites, i, i + 1) + } + } else { + for (i in from downTo to + 1) { + Collections.swap(favorites, i, i - 1) + } + } + favoritesAdapter.notifyItemMoved(from, to) + } +} + +/** + * Compares [CharSequence] as [String]. + * + * It will have empty strings as the first element + */ +class CharSequenceComparator : Comparator<CharSequence> { + override fun compare(p0: CharSequence?, p1: CharSequence?): Int { + if (p0 == null && p1 == null) return 0 + else if (p0 == null && p1 != null) return -1 + else if (p0 != null && p1 == null) return 1 + return p0.toString().compareTo(p1.toString()) + } +} + +/** + * Wrapper classes for the different types of elements shown in the [RecyclerView]s in + * [ControlsFavoritingActivity]. + */ +sealed class ElementWrapper +data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper() +data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt new file mode 100644 index 000000000000..44f3fbc58eaf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +interface Behavior { + fun apply(cvh: ControlViewHolder, cws: ControlWithState) +} 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 81b5f3698567..78e0e8b81b44 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -17,36 +17,34 @@ package com.android.systemui.controls.ui import android.content.Context +import android.content.Intent import android.graphics.drawable.ClipDrawable -import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.Icon import android.graphics.drawable.LayerDrawable import android.service.controls.Control -import android.service.controls.DeviceTypes -import android.service.controls.actions.BooleanAction import android.service.controls.actions.ControlAction -import android.service.controls.actions.FloatAction import android.service.controls.templates.ControlTemplate -import android.service.controls.templates.RangeTemplate +import android.service.controls.templates.TemperatureControlTemplate import android.service.controls.templates.ToggleRangeTemplate import android.service.controls.templates.ToggleTemplate -import android.util.TypedValue -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.R -private const val MIN_LEVEL = 0 -private const val MAX_LEVEL = 10000 +const val MIN_LEVEL = 0 +const val MAX_LEVEL = 10000 +private const val UPDATE_DELAY_IN_MILLIS = 3000L class ControlViewHolder( val layout: ViewGroup, - val controlsController: ControlsController + val controlsController: ControlsController, + val uiExecutor: DelayableExecutor ) { val icon: ImageView = layout.requireViewById(R.id.icon) val status: TextView = layout.requireViewById(R.id.status) @@ -57,6 +55,7 @@ class ControlViewHolder( val clipLayer: ClipDrawable val gd: GradientDrawable lateinit var cws: ControlWithState + var cancelUpdate: Runnable? = null init { val ld = layout.getBackground() as LayerDrawable @@ -68,6 +67,8 @@ class ControlViewHolder( fun bindData(cws: ControlWithState) { this.cws = cws + cancelUpdate?.run() + val (status, template) = cws.control?.let { title.setText(it.getTitle()) subtitle.setText(it.getSubtitle()) @@ -78,9 +79,40 @@ class ControlViewHolder( Pair(Control.STATUS_UNKNOWN, ControlTemplate.NO_TEMPLATE) } + cws.control?.let { c -> + layout.setOnLongClickListener(View.OnLongClickListener() { + val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + context.sendBroadcast(closeDialog) + + c.getAppIntent().send() + true + }) + } + findBehavior(status, template).apply(this, cws) } + fun actionResponse(@ControlAction.ResponseResult response: Int) { + val text = when (response) { + ControlAction.RESPONSE_OK -> "Success" + ControlAction.RESPONSE_FAIL -> "Error" + else -> "" + } + + if (!text.isEmpty()) { + val previousText = status.getText() + val previousTextExtra = statusExtra.getText() + + cancelUpdate = uiExecutor.executeDelayed({ + status.setText(previousText) + statusExtra.setText(previousTextExtra) + }, UPDATE_DELAY_IN_MILLIS) + + status.setText(text) + statusExtra.setText("") + } + } + fun action(action: ControlAction) { controlsController.action(cws.ci, action) } @@ -88,13 +120,14 @@ class ControlViewHolder( private fun findBehavior(status: Int, template: ControlTemplate): Behavior { return when { status == Control.STATUS_UNKNOWN -> UnknownBehavior() - template is ToggleTemplate -> ToggleTemplateBehavior() - template is ToggleRangeTemplate -> ToggleRangeTemplateBehavior() + template is ToggleTemplate -> ToggleBehavior() + template is ToggleRangeTemplate -> ToggleRangeBehavior() + template is TemperatureControlTemplate -> TemperatureControlBehavior() else -> { object : Behavior { override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { cvh.status.setText(cws.control?.getStatusText()) - cvh.applyRenderInfo(findRenderInfo(cws.ci.deviceType, false)) + cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false)) } } } @@ -118,283 +151,3 @@ class ControlViewHolder( icon.setEnabled(enabled) } } - -private interface Behavior { - fun apply(cvh: ControlViewHolder, cws: ControlWithState) - - fun findRenderInfo(deviceType: Int, isActive: Boolean): RenderInfo = - deviceRenderMap.getOrDefault(deviceType, unknownDeviceMap).getValue(isActive) -} - -private class UnknownBehavior : Behavior { - override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { - cvh.status.setText("Loading...") - cvh.applyRenderInfo(findRenderInfo(cws.ci.deviceType, false)) - } -} - -private class ToggleRangeTemplateBehavior : Behavior { - lateinit var clipLayer: Drawable - lateinit var template: ToggleRangeTemplate - lateinit var control: Control - lateinit var cvh: ControlViewHolder - lateinit var rangeTemplate: RangeTemplate - lateinit var statusExtra: TextView - lateinit var status: TextView - lateinit var context: Context - - override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { - this.control = cws.control!! - this.cvh = cvh - - statusExtra = cvh.statusExtra - status = cvh.status - - status.setText(control.getStatusText()) - - context = status.getContext() - - cvh.layout.setOnTouchListener(ToggleRangeTouchListener()) - - val ld = cvh.layout.getBackground() as LayerDrawable - clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) - - template = control.getControlTemplate() as ToggleRangeTemplate - rangeTemplate = template.getRange() - - val checked = template.isChecked() - val deviceType = control.getDeviceType() - - updateRange((rangeTemplate.getCurrentValue() / 100.0f), checked) - - cvh.setEnabled(checked) - cvh.applyRenderInfo(findRenderInfo(deviceType, checked)) - } - - fun toggle() { - cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) - - val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL - clipLayer.setLevel(nextLevel) - } - - fun beginUpdateRange() { - status.setVisibility(View.GONE) - statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() - .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat()) - } - - fun updateRange(f: Float, checked: Boolean) { - clipLayer.setLevel(if (checked) (MAX_LEVEL * f).toInt() else MIN_LEVEL) - - if (checked && f < 100.0f && f > 0.0f) { - statusExtra.setText("" + (f * 100.0).toInt() + "%") - statusExtra.setVisibility(View.VISIBLE) - } else { - statusExtra.setText("") - statusExtra.setVisibility(View.GONE) - } - } - - fun endUpdateRange(f: Float) { - statusExtra.setText(" - " + (f * 100.0).toInt() + "%") - - val newValue = rangeTemplate.getMinValue() + - (f * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())) - - statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() - .getDimensionPixelSize(R.dimen.control_status_normal).toFloat()) - status.setVisibility(View.VISIBLE) - - cvh.action(FloatAction(rangeTemplate.getTemplateId(), findNearestStep(newValue))) - } - - fun findNearestStep(value: Float): Float { - var minDiff = 1000f - - var f = rangeTemplate.getMinValue() - while (f <= rangeTemplate.getMaxValue()) { - val currentDiff = Math.abs(value - f) - if (currentDiff < minDiff) { - minDiff = currentDiff - } else { - return f - rangeTemplate.getStepValue() - } - - f += rangeTemplate.getStepValue() - } - - return rangeTemplate.getMaxValue() - } - - inner class ToggleRangeTouchListener() : View.OnTouchListener { - private var initialTouchX: Float = 0.0f - private var initialTouchY: Float = 0.0f - private var isDragging: Boolean = false - private val minDragDiff = 20 - - override fun onTouch(v: View, e: MotionEvent): Boolean { - when (e.getActionMasked()) { - MotionEvent.ACTION_DOWN -> setupTouch(e) - MotionEvent.ACTION_MOVE -> detectDrag(v, e) - MotionEvent.ACTION_UP -> endTouch(v, e) - } - - return true - } - - private fun setupTouch(e: MotionEvent) { - initialTouchX = e.getX() - initialTouchY = e.getY() - } - - private fun detectDrag(v: View, e: MotionEvent) { - val xDiff = Math.abs(e.getX() - initialTouchX) - val yDiff = Math.abs(e.getY() - initialTouchY) - - if (xDiff < minDragDiff) { - isDragging = false - } else { - if (!isDragging) { - this@ToggleRangeTemplateBehavior.beginUpdateRange() - } - v.getParent().requestDisallowInterceptTouchEvent(true) - isDragging = true - if (yDiff > xDiff) { - endTouch(v, e) - } else { - val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) - this@ToggleRangeTemplateBehavior.updateRange(percent, true) - } - } - } - - private fun endTouch(v: View, e: MotionEvent) { - if (!isDragging) { - this@ToggleRangeTemplateBehavior.toggle() - } else { - val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) - this@ToggleRangeTemplateBehavior.endUpdateRange(percent) - } - - initialTouchX = 0.0f - initialTouchY = 0.0f - isDragging = false - } - } -} - -private class ToggleTemplateBehavior : Behavior { - lateinit var clipLayer: Drawable - lateinit var template: ToggleTemplate - lateinit var control: Control - lateinit var cvh: ControlViewHolder - lateinit var context: Context - lateinit var status: TextView - - override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { - this.control = cws.control!! - this.cvh = cvh - status = cvh.status - - status.setText(control.getStatusText()) - - cvh.layout.setOnClickListener(View.OnClickListener() { toggle() }) - - val ld = cvh.layout.getBackground() as LayerDrawable - clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) - - template = control.getControlTemplate() as ToggleTemplate - - val checked = template.isChecked() - val deviceType = control.getDeviceType() - - clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL) - cvh.setEnabled(checked) - cvh.applyRenderInfo(findRenderInfo(deviceType, checked)) - } - - fun toggle() { - cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) - - val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL - clipLayer.setLevel(nextLevel) - } -} - -internal data class RenderInfo(val iconResourceId: Int, val foreground: Int, val background: Int) - -private val unknownDeviceMap = mapOf( - false to RenderInfo( - R.drawable.ic_light_off_gm2_24px, - R.color.unknown_foreground, - R.color.unknown_foreground), - true to RenderInfo( - R.drawable.ic_lightbulb_outline_gm2_24px, - R.color.unknown_foreground, - R.color.unknown_foreground) -) - -private val deviceRenderMap = mapOf<Int, Map<Boolean, RenderInfo>>( - DeviceTypes.TYPE_UNKNOWN to unknownDeviceMap, - DeviceTypes.TYPE_LIGHT to mapOf( - false to RenderInfo( - R.drawable.ic_light_off_gm2_24px, - R.color.light_foreground, - R.color.light_background), - true to RenderInfo( - R.drawable.ic_lightbulb_outline_gm2_24px, - R.color.light_foreground, - R.color.light_background) - ), - DeviceTypes.TYPE_THERMOSTAT to mapOf( - false to RenderInfo( - R.drawable.ic_device_thermostat_gm2_24px, - R.color.light_foreground, - R.color.light_background), - true to RenderInfo( - R.drawable.ic_device_thermostat_gm2_24px, - R.color.light_foreground, - R.color.light_background) - ), - DeviceTypes.TYPE_CAMERA to mapOf( - false to RenderInfo( - R.drawable.ic_videocam_gm2_24px, - R.color.light_foreground, - R.color.light_background), - true to RenderInfo( - R.drawable.ic_videocam_gm2_24px, - R.color.light_foreground, - R.color.light_background) - ), - DeviceTypes.TYPE_LOCK to mapOf( - false to RenderInfo( - R.drawable.ic_lock_open_gm2_24px, - R.color.lock_foreground, - R.color.lock_background), - true to RenderInfo( - R.drawable.ic_lock_gm2_24px, - R.color.lock_foreground, - R.color.lock_background) - ), - DeviceTypes.TYPE_SWITCH to mapOf( - false to RenderInfo( - R.drawable.ic_switches_gm2_24px, - R.color.lock_foreground, - R.color.lock_background), - true to RenderInfo( - R.drawable.ic_switches_gm2_24px, - R.color.lock_foreground, - R.color.lock_background) - ), - DeviceTypes.TYPE_OUTLET to mapOf( - false to RenderInfo( - R.drawable.ic_power_off_gm2_24px, - R.color.lock_foreground, - R.color.lock_background), - true to RenderInfo( - R.drawable.ic_power_gm2_24px, - R.color.lock_foreground, - R.color.lock_background) - ) -) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index b07a75d5e757..d70c86fc3266 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -22,6 +22,8 @@ import android.service.controls.actions.ControlAction import android.view.ViewGroup interface ControlsUiController { + val available: Boolean + fun show(parent: ViewGroup) fun hide() fun onRefreshState(componentName: ComponentName, controls: List<Control>) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 926fb6e75594..f029dfbe1bb2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -22,6 +22,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.graphics.drawable.Drawable import android.os.IBinder import android.service.controls.Control import android.service.controls.TokenProvider @@ -29,17 +30,24 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.Space +import com.android.settingslib.widget.CandidateInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.R +import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy -import java.util.concurrent.Executor +import java.text.Collator + import javax.inject.Inject import javax.inject.Singleton @@ -102,18 +110,41 @@ class TokenProviderConnection(val cc: ControlsController, val context: Context) } } +private data class ControlKey(val componentName: ComponentName, val controlId: String) + @Singleton class ControlsUiControllerImpl @Inject constructor ( val controlsController: Lazy<ControlsController>, val context: Context, - @Main val uiExecutor: Executor + @Main val uiExecutor: DelayableExecutor, + @Background val bgExecutor: DelayableExecutor, + val controlsListingController: Lazy<ControlsListingController> ) : ControlsUiController { private lateinit var controlInfos: List<ControlInfo> - private val controlsById = mutableMapOf<Pair<ComponentName, String>, ControlWithState>() - private val controlViewsById = mutableMapOf<String, ControlViewHolder>() + private val controlsById = mutableMapOf<ControlKey, ControlWithState>() + private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>() private lateinit var parent: ViewGroup + override val available: Boolean + get() = controlsController.get().available + + private val listingCallback = object : ControlsListingController.ControlsListingCallback { + override fun onServicesUpdated(candidates: List<CandidateInfo>) { + bgExecutor.execute { + val collator = Collator.getInstance(context.getResources() + .getConfiguration().locale) + val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) { + it.loadLabel() + } + + val mList = candidates.toMutableList() + mList.sortWith(localeComparator) + loadInitialSetupViewIcons(mList.map { it.loadLabel() to it.loadIcon() }) + } + } + } + override fun show(parent: ViewGroup) { Log.d(TAG, "show()") @@ -123,7 +154,7 @@ class ControlsUiControllerImpl @Inject constructor ( controlInfos.map { ControlWithState(it, null) - }.associateByTo(controlsById) { Pair(it.ci.component, it.ci.controlId) } + }.associateByTo(controlsById) { ControlKey(it.ci.component, it.ci.controlId) } if (controlInfos.isEmpty()) { showInitialSetupView() @@ -136,18 +167,46 @@ class ControlsUiControllerImpl @Inject constructor ( val serviceIntent = Intent() serviceIntent.setComponent(ComponentName("com.android.systemui.home.mock", "com.android.systemui.home.mock.AuthService")) - context.bindService(serviceIntent, tokenProviderConnection!!, Context.BIND_AUTO_CREATE) + if (!context.bindService(serviceIntent, tokenProviderConnection!!, + Context.BIND_AUTO_CREATE)) { + controlsController.get().subscribeToFavorites() + } } private fun showInitialSetupView() { val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.controls_no_favorites, parent, true) - val textView = parent.requireViewById(R.id.controls_title) as TextView - textView.setOnClickListener { + val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup + viewGroup.setOnClickListener(launchSelectorActivityListener(context)) + + controlsListingController.get().addCallback(listingCallback) + } + + private fun loadInitialSetupViewIcons(icons: List<Pair<CharSequence, Drawable>>) { + uiExecutor.execute { + val viewGroup = parent.requireViewById(R.id.controls_icon_row) as ViewGroup + viewGroup.removeAllViews() + + val inflater = LayoutInflater.from(context) + icons.forEach { + val imageView = inflater.inflate(R.layout.controls_icon, viewGroup, false) + as ImageView + imageView.setContentDescription(it.first) + imageView.setImageDrawable(it.second) + viewGroup.addView(imageView) + } + } + } + + private fun launchSelectorActivityListener(context: Context): (View) -> Unit { + return { _ -> + val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + context.sendBroadcast(closeDialog) + val i = Intent() i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java)) - i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(i) } } @@ -166,18 +225,18 @@ class ControlsUiControllerImpl @Inject constructor ( val item = inflater.inflate( R.layout.controls_base_item, lastRow, false) as ViewGroup lastRow.addView(item) - val cvh = ControlViewHolder(item, controlsController.get()) - cvh.bindData(controlsById.get(Pair(it.component, it.controlId))!!) - controlViewsById.put(it.controlId, cvh) + val cvh = ControlViewHolder(item, controlsController.get(), uiExecutor) + val key = ControlKey(it.component, it.controlId) + cvh.bindData(controlsById.getValue(key)) + controlViewsById.put(key, cvh) } - val moreImageView = parent.requireViewById(R.id.controls_more) as View - moreImageView.setOnClickListener { - val i = Intent() - i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java)) - i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - context.startActivity(i) + if ((controlInfos.size % 2) == 1) { + lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f)) } + + val moreImageView = parent.requireViewById(R.id.controls_more) as View + moreImageView.setOnClickListener(launchSelectorActivityListener(context)) } override fun hide() { @@ -189,31 +248,35 @@ class ControlsUiControllerImpl @Inject constructor ( parent.removeAllViews() controlsById.clear() controlViewsById.clear() + controlsListingController.get().removeCallback(listingCallback) } override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { Log.d(TAG, "onRefreshState()") controls.forEach { c -> - controlsById.get(Pair(componentName, c.getControlId()))?.let { + controlsById.get(ControlKey(componentName, c.getControlId()))?.let { Log.d(TAG, "onRefreshState() for id: " + c.getControlId()) val cws = ControlWithState(it.ci, c) - controlsById.put(Pair(componentName, c.getControlId()), cws) + val key = ControlKey(componentName, c.getControlId()) + controlsById.put(key, cws) uiExecutor.execute { - controlViewsById.get(c.getControlId())?.bindData(cws) + controlViewsById.get(key)?.bindData(cws) } } } } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { - Log.d(TAG, "onActionResponse()") - TODO("not implemented") + val key = ControlKey(componentName, controlId) + uiExecutor.execute { + controlViewsById.get(key)?.actionResponse(response) + } } - private fun createRow(inflater: LayoutInflater, parent: ViewGroup): ViewGroup { - val row = inflater.inflate(R.layout.controls_row, parent, false) as ViewGroup - parent.addView(row) + private fun createRow(inflater: LayoutInflater, listView: ViewGroup): ViewGroup { + val row = inflater.inflate(R.layout.controls_row, listView, false) as ViewGroup + listView.addView(row) return row } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt new file mode 100644 index 000000000000..da52c6f8ee21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.service.controls.DeviceTypes +import android.service.controls.templates.TemperatureControlTemplate + +import com.android.systemui.R + +data class IconState(val disabledResourceId: Int, val enabledResourceId: Int) { + operator fun get(state: Boolean): Int { + return if (state) { + enabledResourceId + } else { + disabledResourceId + } + } +} + +data class RenderInfo(val iconResourceId: Int, val foreground: Int, val background: Int) { + + companion object { + fun lookup(deviceType: Int, enabled: Boolean): RenderInfo { + val iconState = deviceIconMap.getValue(deviceType) + val (fg, bg) = deviceColorMap.getValue(deviceType) + return RenderInfo(iconState[enabled], fg, bg) + } + + fun lookup(deviceType: Int, offset: Int, enabled: Boolean): RenderInfo { + val key = deviceType * BUCKET_SIZE + offset + return lookup(key, enabled) + } + } +} + +private const val BUCKET_SIZE = 1000 +private const val THERMOSTAT_RANGE = DeviceTypes.TYPE_THERMOSTAT * BUCKET_SIZE + +private val deviceColorMap = mapOf<Int, Pair<Int, Int>>( + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to + Pair(R.color.thermo_heat_foreground, R.color.thermo_heat_background), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to + Pair(R.color.thermo_cool_foreground, R.color.thermo_cool_background), + DeviceTypes.TYPE_LIGHT to Pair(R.color.light_foreground, R.color.light_background) +).withDefault { + Pair(R.color.control_foreground, R.color.control_background) +} + +private val deviceIconMap = mapOf<Int, IconState>( + THERMOSTAT_RANGE to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_OFF) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT_COOL) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_ECO) to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + DeviceTypes.TYPE_THERMOSTAT to IconState( + R.drawable.ic_device_thermostat_gm2_24px, + R.drawable.ic_device_thermostat_gm2_24px + ), + DeviceTypes.TYPE_LIGHT to IconState( + R.drawable.ic_light_off_gm2_24px, + R.drawable.ic_lightbulb_outline_gm2_24px + ), + DeviceTypes.TYPE_CAMERA to IconState( + R.drawable.ic_videocam_gm2_24px, + R.drawable.ic_videocam_gm2_24px + ), + DeviceTypes.TYPE_LOCK to IconState( + R.drawable.ic_lock_open_gm2_24px, + R.drawable.ic_lock_gm2_24px + ), + DeviceTypes.TYPE_SWITCH to IconState( + R.drawable.ic_switches_gm2_24px, + R.drawable.ic_switches_gm2_24px + ), + DeviceTypes.TYPE_OUTLET to IconState( + R.drawable.ic_power_off_gm2_24px, + R.drawable.ic_power_gm2_24px + ), + DeviceTypes.TYPE_VACUUM to IconState( + R.drawable.ic_vacuum_gm2_24px, + R.drawable.ic_vacuum_gm2_24px + ), + DeviceTypes.TYPE_MOP to IconState( + R.drawable.ic_vacuum_gm2_24px, + R.drawable.ic_vacuum_gm2_24px + ) +).withDefault { + IconState( + R.drawable.ic_device_unknown_gm2_24px, + R.drawable.ic_device_unknown_gm2_24px + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt new file mode 100644 index 000000000000..ae0ebbb9e1bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.service.controls.Control +import android.service.controls.templates.TemperatureControlTemplate +import android.widget.TextView + +import com.android.systemui.R + +class TemperatureControlBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var template: TemperatureControlTemplate + lateinit var status: TextView + lateinit var context: Context + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + status = cvh.status + + status.setText(control.getStatusText()) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as TemperatureControlTemplate + + val activeMode = template.getCurrentActiveMode() + val enabled = activeMode != 0 && activeMode != TemperatureControlTemplate.MODE_OFF + val deviceType = control.getDeviceType() + + clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL) + cvh.setEnabled(enabled) + cvh.applyRenderInfo(RenderInfo.lookup(deviceType, activeMode, enabled)) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt new file mode 100644 index 000000000000..7cd3ab795678 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.view.View +import android.widget.TextView +import android.service.controls.Control +import android.service.controls.actions.BooleanAction +import android.service.controls.templates.ToggleTemplate + +import com.android.systemui.R + +class ToggleBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var template: ToggleTemplate + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var context: Context + lateinit var status: TextView + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + status = cvh.status + + status.setText(control.getStatusText()) + + cvh.layout.setOnClickListener(View.OnClickListener() { toggle() }) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as ToggleTemplate + + val checked = template.isChecked() + val deviceType = control.getDeviceType() + + clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL) + cvh.setEnabled(checked) + cvh.applyRenderInfo(RenderInfo.lookup(deviceType, checked)) + } + + fun toggle() { + cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) + + val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL + clipLayer.setLevel(nextLevel) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt new file mode 100644 index 000000000000..a6918f50a977 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.view.MotionEvent +import android.view.View +import android.widget.TextView +import android.service.controls.Control +import android.service.controls.actions.BooleanAction +import android.service.controls.actions.FloatAction +import android.service.controls.templates.RangeTemplate +import android.service.controls.templates.ToggleRangeTemplate +import android.util.TypedValue + +import com.android.systemui.R + +class ToggleRangeBehavior : Behavior { + lateinit var clipLayer: Drawable + lateinit var template: ToggleRangeTemplate + lateinit var control: Control + lateinit var cvh: ControlViewHolder + lateinit var rangeTemplate: RangeTemplate + lateinit var statusExtra: TextView + lateinit var status: TextView + lateinit var context: Context + + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + this.control = cws.control!! + this.cvh = cvh + + statusExtra = cvh.statusExtra + status = cvh.status + + status.setText(control.getStatusText()) + + context = status.getContext() + + cvh.layout.setOnTouchListener(ToggleRangeTouchListener()) + + val ld = cvh.layout.getBackground() as LayerDrawable + clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) + + template = control.getControlTemplate() as ToggleRangeTemplate + rangeTemplate = template.getRange() + + val checked = template.isChecked() + val deviceType = control.getDeviceType() + + updateRange((rangeTemplate.getCurrentValue() / 100.0f), checked) + + cvh.setEnabled(checked) + cvh.applyRenderInfo(RenderInfo.lookup(deviceType, checked)) + } + + fun toggle() { + cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked())) + + val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL + clipLayer.setLevel(nextLevel) + } + + fun beginUpdateRange() { + status.setVisibility(View.GONE) + statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat()) + } + + fun updateRange(f: Float, checked: Boolean) { + clipLayer.setLevel(if (checked) (MAX_LEVEL * f).toInt() else MIN_LEVEL) + + if (checked && f < 100.0f && f > 0.0f) { + statusExtra.setText("" + (f * 100.0).toInt() + "%") + statusExtra.setVisibility(View.VISIBLE) + } else { + statusExtra.setText("") + statusExtra.setVisibility(View.GONE) + } + } + + fun endUpdateRange(f: Float) { + statusExtra.setText(" - " + (f * 100.0).toInt() + "%") + + val newValue = rangeTemplate.getMinValue() + + (f * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())) + + statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources() + .getDimensionPixelSize(R.dimen.control_status_normal).toFloat()) + status.setVisibility(View.VISIBLE) + + cvh.action(FloatAction(rangeTemplate.getTemplateId(), findNearestStep(newValue))) + } + + fun findNearestStep(value: Float): Float { + var minDiff = 1000f + + var f = rangeTemplate.getMinValue() + while (f <= rangeTemplate.getMaxValue()) { + val currentDiff = Math.abs(value - f) + if (currentDiff < minDiff) { + minDiff = currentDiff + } else { + return f - rangeTemplate.getStepValue() + } + + f += rangeTemplate.getStepValue() + } + + return rangeTemplate.getMaxValue() + } + + inner class ToggleRangeTouchListener() : View.OnTouchListener { + private var initialTouchX: Float = 0.0f + private var initialTouchY: Float = 0.0f + private var isDragging: Boolean = false + private val minDragDiff = 20 + + override fun onTouch(v: View, e: MotionEvent): Boolean { + when (e.getActionMasked()) { + MotionEvent.ACTION_DOWN -> setupTouch(e) + MotionEvent.ACTION_MOVE -> detectDrag(v, e) + MotionEvent.ACTION_UP -> endTouch(v, e) + } + + return true + } + + private fun setupTouch(e: MotionEvent) { + initialTouchX = e.getX() + initialTouchY = e.getY() + } + + private fun detectDrag(v: View, e: MotionEvent) { + val xDiff = Math.abs(e.getX() - initialTouchX) + val yDiff = Math.abs(e.getY() - initialTouchY) + + if (xDiff < minDragDiff) { + isDragging = false + } else { + if (!isDragging) { + this@ToggleRangeBehavior.beginUpdateRange() + } + v.getParent().requestDisallowInterceptTouchEvent(true) + isDragging = true + if (yDiff > xDiff) { + endTouch(v, e) + } else { + val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) + this@ToggleRangeBehavior.updateRange(percent, true) + } + } + } + + private fun endTouch(v: View, e: MotionEvent) { + if (!isDragging) { + this@ToggleRangeBehavior.toggle() + } else { + val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth())) + this@ToggleRangeBehavior.endUpdateRange(percent) + } + + initialTouchX = 0.0f + initialTouchY = 0.0f + isDragging = false + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt new file mode 100644 index 000000000000..5a6e5b481544 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +class UnknownBehavior : Behavior { + override fun apply(cvh: ControlViewHolder, cws: ControlWithState) { + cvh.status.setText("Loading...") + cvh.setEnabled(false) + cvh.applyRenderInfo(RenderInfo.lookup(cws.ci.deviceType, false)) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultComponentBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultComponentBinder.java index 18fe3ec92827..50252460d5c1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultComponentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultComponentBinder.java @@ -25,7 +25,6 @@ import dagger.Module; */ @Module(includes = {DefaultActivityBinder.class, DefaultBroadcastReceiverBinder.class, - DefaultServiceBinder.class, - SystemUIBinder.class}) + DefaultServiceBinder.class}) public abstract class DefaultComponentBinder { } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 53a23b89f943..0ec739fecaa7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -30,6 +30,7 @@ import android.view.IWindowManager; import android.view.LayoutInflater; import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.dagger.qualifiers.Background; @@ -191,6 +192,12 @@ public class DependencyProvider { return new AlwaysOnDisplayPolicy(context); } + /***/ + @Provides + public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) { + return new NotificationMessagingUtil(context); + } + /** */ @Provides public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index 5c171e41db41..a57ec5b483dd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -56,7 +56,7 @@ import dagger.Provides; * overridden by the System UI implementation. */ @Module(includes = {DividerModule.class}) -abstract class SystemUIDefaultModule { +public abstract class SystemUIDefaultModule { @Singleton @Provides diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index f793b3df92a4..f068d9c10e86 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -38,7 +38,9 @@ import com.android.systemui.statusbar.StatusBarWindowBlurController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.people.PeopleHubModule; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; @@ -64,9 +66,12 @@ import dagger.Provides; AssistModule.class, ConcurrencyModule.class, LogModule.class, + NotificationsModule.class, PeopleHubModule.class, }, - subcomponents = {StatusBarComponent.class, NotificationRowComponent.class}) + subcomponents = {StatusBarComponent.class, + NotificationRowComponent.class, + ExpandableNotificationRowComponent.class}) public abstract class SystemUIModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java index e8509b366c5b..3bf5ad759267 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java @@ -45,6 +45,7 @@ import dagger.Component; DependencyBinder.class, SystemServicesModule.class, SystemUIFactory.ContextHolder.class, + SystemUIBinder.class, SystemUIModule.class, SystemUIDefaultModule.class}) public interface SystemUIRootComponent { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 43db85bd91ec..6f655bb0b209 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -118,6 +118,7 @@ public class DozeFactory { new DozeWallpaperState(mWallpaperManager, mBiometricUnlockController, mDozeParameters), new DozeDockHandler(config, machine, mDockManager), + new DozeSuppressedHandler(dozeService, config, machine), new DozeAuthRemover(dozeService) }); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 8afdf1aeb023..96ae6c9ec4a2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -266,6 +266,14 @@ public class DozeLog implements Dumpable { mLogger.logSensorTriggered(reason); } + /** + * Appends doze suppressed event to the logs + * @param suppressedState The {@link DozeMachine.State} that was suppressed + */ + public void traceDozeSuppressed(DozeMachine.State suppressedState) { + mLogger.logDozeSuppressed(suppressedState); + } + private class SummaryStats { private int mCount; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 42decd592071..732745a1158b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -194,6 +194,14 @@ class DozeLogger @Inject constructor( "Sensor triggered, type=${reasonToString(int1)}" }) } + + fun logDozeSuppressed(state: DozeMachine.State) { + buffer.log(TAG, INFO, { + str1 = state.name + }, { + "Doze state suppressed, state=$str1" + }) + } } private const val TAG = "DozeLog" diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 03c25eee4f6f..6e81d3a11098 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -102,6 +102,10 @@ public class DozeMachine { } } + boolean isAlwaysOn() { + return this == DOZE_AOD || this == DOZE_AOD_DOCKED; + } + int screenState(DozeParameters parameters) { switch (this) { case UNINITIALIZED: @@ -324,6 +328,11 @@ public class DozeMachine { if (mState == State.FINISH) { return State.FINISH; } + if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT) && requestedState.isAlwaysOn()) { + Log.i(TAG, "Doze is suppressed. Suppressing state: " + requestedState); + mDozeLog.traceDozeSuppressed(requestedState); + return State.DOZE; + } if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING || mState == State.DOZE_AOD || mState == State.DOZE) && requestedState == State.DOZE_PULSE_DONE) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 3abeea91cdee..e50f1fb2a3ee 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -18,7 +18,6 @@ package com.android.systemui.doze; import static com.android.systemui.doze.DozeMachine.State.DOZE; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; -import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING; import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; @@ -90,10 +89,10 @@ public class DozeScreenState implements DozeMachine.Part { } final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState); - final boolean pulseEnding = oldState == DOZE_PULSE_DONE && isAlwaysOnState(newState); + final boolean pulseEnding = oldState == DOZE_PULSE_DONE && newState.isAlwaysOn(); final boolean turningOn = (oldState == DOZE_AOD_PAUSED || oldState == DOZE) - && isAlwaysOnState(newState); - final boolean turningOff = (isAlwaysOnState(oldState) && newState == DOZE) + && newState.isAlwaysOn(); + final boolean turningOff = (newState.isAlwaysOn() && newState == DOZE) || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED); final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED; if (messagePending || justInitialized || pulseEnding || turningOn) { @@ -132,10 +131,6 @@ public class DozeScreenState implements DozeMachine.Part { } } - private boolean isAlwaysOnState(DozeMachine.State state) { - return state == DOZE_AOD || state == DOZE_AOD_DOCKED; - } - private void applyPendingScreenState() { applyScreenState(mPendingScreenState); mPendingScreenState = Display.STATE_UNKNOWN; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java new file mode 100644 index 000000000000..3a5c1a0890f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressedHandler.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.doze; + +import static java.util.Objects.requireNonNull; + +import android.app.ActivityManager; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.display.AmbientDisplayConfiguration; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** Handles updating the doze state when doze is suppressed. */ +public final class DozeSuppressedHandler implements DozeMachine.Part { + + private static final String TAG = DozeSuppressedHandler.class.getSimpleName(); + private static final boolean DEBUG = DozeService.DEBUG; + + private final ContentResolver mResolver; + private final AmbientDisplayConfiguration mConfig; + private final DozeMachine mMachine; + private final DozeSuppressedSettingObserver mSettingObserver; + private final Handler mHandler = new Handler(); + + public DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config, + DozeMachine machine) { + this(context, config, machine, null); + } + + @VisibleForTesting + DozeSuppressedHandler(Context context, AmbientDisplayConfiguration config, DozeMachine machine, + DozeSuppressedSettingObserver observer) { + mResolver = context.getContentResolver(); + mConfig = requireNonNull(config); + mMachine = requireNonNull(machine); + if (observer == null) { + mSettingObserver = new DozeSuppressedSettingObserver(mHandler); + } else { + mSettingObserver = observer; + } + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + switch (newState) { + case INITIALIZED: + mSettingObserver.register(); + break; + case FINISH: + mSettingObserver.unregister(); + break; + default: + // no-op + } + } + + /** + * Listens to changes to the DOZE_SUPPRESSED secure setting and updates the doze state + * accordingly. + */ + final class DozeSuppressedSettingObserver extends ContentObserver { + private boolean mRegistered; + + private DozeSuppressedSettingObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + if (userId != ActivityManager.getCurrentUser()) { + return; + } + final DozeMachine.State nextState; + if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) + && !mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) { + nextState = DozeMachine.State.DOZE_AOD; + } else { + nextState = DozeMachine.State.DOZE; + } + mMachine.requestState(nextState); + } + + void register() { + if (mRegistered) { + return; + } + mResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SUPPRESS_DOZE), + false, this, UserHandle.USER_CURRENT); + Log.d(TAG, "Register"); + mRegistered = true; + } + + void unregister() { + if (!mRegistered) { + return; + } + mResolver.unregisterContentObserver(this); + Log.d(TAG, "Unregister"); + mRegistered = false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index b9c056df89a1..305a4c870d91 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -127,6 +127,11 @@ public class DozeTriggers implements DozeMachine.Part { mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; } + if (mConfig.dozeSuppressed(UserHandle.USER_CURRENT)) { + runIfNotNull(onPulseSuppressedListener); + mDozeLog.tracePulseDropped("dozeSuppressed"); + return; + } requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */, onPulseSuppressedListener); mDozeLog.traceNotificationPulse(); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 80d776a59235..a3cd5fdd771b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -40,6 +40,7 @@ import android.content.IntentFilter; import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; @@ -100,7 +101,6 @@ import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.EmergencyDialerConstants; @@ -459,12 +459,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mKeyguardManager.isDeviceLocked()) : null; - boolean showControls = !mKeyguardManager.isDeviceLocked() && isControlsEnabled(mContext); - ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController, mBlurUtils, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - showControls ? mControlsUiController : null); + shouldShowControls() ? mControlsUiController : null); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setKeyguardShowing(mKeyguardShowing); @@ -538,7 +536,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean shouldBeSeparated() { - return !isControlsEnabled(mContext); + return !shouldShowControls(); } @Override @@ -1047,7 +1045,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); view.setOnClickListener(v -> onClickItem(position)); - view.setOnLongClickListener(v -> onLongClickItem(position)); + if (action instanceof LongPressAction) { + view.setOnLongClickListener(v -> onLongClickItem(position)); + } return view; } @@ -1135,7 +1135,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * A single press action maintains no state, just responds to a press * and takes an action. */ - private static abstract class SinglePressAction implements Action { + + private abstract class SinglePressAction implements Action { private final int mIconResId; private final Drawable mIcon; private final int mMessageResId; @@ -1174,7 +1175,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } protected int getActionLayoutId(Context context) { - if (isControlsEnabled(context)) { + if (shouldShowControls()) { return com.android.systemui.R.layout.global_actions_grid_item_v2; } return com.android.systemui.R.layout.global_actions_grid_item; @@ -1640,8 +1641,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); panelContainer.addView(mPanelController.getPanelContent(), panelParams); - mBackgroundDrawable = mPanelController.getBackgroundDrawable(); - mScrimAlpha = 1f; } } @@ -1669,7 +1668,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } if (mBackgroundDrawable == null) { mBackgroundDrawable = new ScrimDrawable(); - mScrimAlpha = ScrimController.GRADIENT_SCRIM_ALPHA; + mScrimAlpha = 0.8f; } getWindow().setBackgroundDrawable(mBackgroundDrawable); } @@ -1728,7 +1727,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!(mBackgroundDrawable instanceof ScrimDrawable)) { return; } - ((ScrimDrawable) mBackgroundDrawable).setColor(colors.getMainColor(), animate); + ((ScrimDrawable) mBackgroundDrawable).setColor(colors.supportsDarkText() ? Color.WHITE + : Color.BLACK, animate); View decorView = getWindow().getDecorView(); if (colors.supportsDarkText()) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | @@ -1804,6 +1804,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, void dismissImmediately() { mShowing = false; + if (mControlsUiController != null) mControlsUiController.hide(); dismissPanel(); resetOrientation(); completeDismiss(); @@ -1900,8 +1901,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return true; } - private static boolean isControlsEnabled(Context context) { - return Settings.Secure.getInt( - context.getContentResolver(), "systemui.controls_available", 0) == 1; + private boolean shouldShowControls() { + return !mKeyguardManager.isDeviceLocked() + && mControlsUiController.getAvailable(); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index dd1856a93d2e..146f36bcd04f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -30,17 +30,15 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.systemui.Dependency; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -49,8 +47,7 @@ import javax.inject.Inject; import dagger.Lazy; -public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, - PluginListener<GlobalActionsPanelPlugin> { +public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks { private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f; @@ -59,38 +56,30 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, private final KeyguardStateController mKeyguardStateController; private final DeviceProvisionedController mDeviceProvisionedController; private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension; - private GlobalActionsPanelPlugin mPlugin; + private final BlurUtils mBlurUtils; private final CommandQueue mCommandQueue; private GlobalActionsDialog mGlobalActionsDialog; private boolean mDisabled; - private final PluginManager mPluginManager; - private final String mPluginPackageName; @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, - Lazy<GlobalActionsDialog> globalActionsDialogLazy) { + Lazy<GlobalActionsDialog> globalActionsDialogLazy, BlurUtils blurUtils) { mContext = context; mGlobalActionsDialogLazy = globalActionsDialogLazy; mKeyguardStateController = Dependency.get(KeyguardStateController.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - mPluginManager = Dependency.get(PluginManager.class); mCommandQueue = commandQueue; + mBlurUtils = blurUtils; mCommandQueue.addCallback(this); mPanelExtension = Dependency.get(ExtensionController.class) .newExtension(GlobalActionsPanelPlugin.class) .withPlugin(GlobalActionsPanelPlugin.class) .build(); - mPluginPackageName = mContext.getString( - com.android.systemui.R.string.config_controlsPluginPackageName); - mPluginManager.addPluginListener( - GlobalActionsPanelPlugin.ACTION, this, GlobalActionsPanelPlugin.class, true); } @Override public void destroy() { mCommandQueue.removeCallback(this); - mPluginManager.removePluginListener(this); - if (mPlugin != null) mPlugin.onDestroy(); if (mGlobalActionsDialog != null) { mGlobalActionsDialog.destroy(); mGlobalActionsDialog = null; @@ -103,14 +92,13 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(), mDeviceProvisionedController.isDeviceProvisioned(), - mPlugin != null ? mPlugin : mPanelExtension.get()); + mPanelExtension.get()); Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth(); } @Override public void showShutdownUi(boolean isReboot, String reason) { ScrimDrawable background = new ScrimDrawable(); - background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255)); Dialog d = new Dialog(mContext, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); @@ -160,8 +148,13 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, reasonView.setText(rebootReasonMessage); } - GradientColors colors = Dependency.get(SysuiColorExtractor.class).getNeutralColors(); - background.setColor(colors.getMainColor(), false); + if (mBlurUtils.supportsBlursOnWindows()) { + background.setAlpha((int) (ScrimController.GRADIENT_SCRIM_ALPHA_BUSY * 255)); + mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(), + mBlurUtils.radiusForRatio(1)); + } else { + background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255)); + } d.show(); } @@ -199,16 +192,4 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialog.dismissDialog(); } } - - @Override - public void onPluginConnected(GlobalActionsPanelPlugin plugin, Context pluginContext) { - if (pluginContext.getPackageName().equals(mPluginPackageName)) { - mPlugin = plugin; - } - } - - @Override - public void onPluginDisconnected(GlobalActionsPanelPlugin plugin) { - mPlugin = null; - } } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java index 657a308224ae..11e215d0d358 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java @@ -153,11 +153,11 @@ public class EglHelper { return true; } - private boolean checkExtensionCapability(String extName) { + boolean checkExtensionCapability(String extName) { return mExts.contains(extName); } - private int getWcgCapability() { + int getWcgCapability() { if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) { return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; } @@ -212,7 +212,7 @@ public class EglHelper { if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) { attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE}; } - mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0); + mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */); } else { Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay() + ", has valid surface=" + surfaceHolder.getSurface().isValid()); @@ -235,6 +235,10 @@ public class EglHelper { return true; } + EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) { + return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset); + } + /** * Destroy EglSurface. */ @@ -242,7 +246,7 @@ public class EglHelper { if (hasEglSurface()) { eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(mEglDisplay, mEglSurface); - mEglSurface = null; + mEglSurface = EGL_NO_SURFACE; } } @@ -296,7 +300,7 @@ public class EglHelper { public void destroyEglContext() { if (hasEglContext()) { eglDestroyContext(mEglDisplay, mEglContext); - mEglContext = null; + mEglContext = EGL_NO_CONTEXT; } } @@ -340,11 +344,16 @@ public class EglHelper { destroyEglContext(); } if (hasEglDisplay()) { - eglTerminate(mEglDisplay); + terminateEglDisplay(); } mEglReady = false; } + void terminateEglDisplay() { + eglTerminate(mEglDisplay); + mEglDisplay = EGL_NO_DISPLAY; + } + /** * Called to dump current state. * @param prefix prefix. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 14eec59211bd..9da99c453022 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -151,7 +151,7 @@ public class KeyguardViewMediator extends SystemUI { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000; - private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final boolean DEBUG = true; private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES; private final static String TAG = "KeyguardViewMediator"; diff --git a/packages/SystemUI/src/com/android/systemui/log/Event.java b/packages/SystemUI/src/com/android/systemui/log/Event.java deleted file mode 100644 index 7bc1abfbb0d8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/Event.java +++ /dev/null @@ -1,84 +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.log; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Stores information about an event that occurred in SystemUI to be used for debugging and triage. - * Every event has a time stamp, log level and message. - * Events are stored in {@link SysuiLog} and can be printed in a dumpsys. - */ -public class Event { - public static final int UNINITIALIZED = -1; - - @IntDef({ERROR, WARN, INFO, DEBUG, VERBOSE}) - @Retention(RetentionPolicy.SOURCE) - public @interface Level {} - public static final int VERBOSE = 2; - public static final int DEBUG = 3; - public static final int INFO = 4; - public static final int WARN = 5; - public static final int ERROR = 6; - public static final @Level int DEFAULT_LOG_LEVEL = DEBUG; - - private long mTimestamp; - private @Level int mLogLevel = DEFAULT_LOG_LEVEL; - private String mMessage = ""; - - /** - * initialize an event with a message - */ - public Event init(String message) { - init(DEFAULT_LOG_LEVEL, message); - return this; - } - - /** - * initialize an event with a logLevel and message - */ - public Event init(@Level int logLevel, String message) { - mTimestamp = System.currentTimeMillis(); - mLogLevel = logLevel; - mMessage = message; - return this; - } - - public String getMessage() { - return mMessage; - } - - public long getTimestamp() { - return mTimestamp; - } - - public @Level int getLogLevel() { - return mLogLevel; - } - - /** - * Recycle this event - */ - void recycle() { - mTimestamp = -1; - mLogLevel = DEFAULT_LOG_LEVEL; - mMessage = ""; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java deleted file mode 100644 index 470f2b0d1b98..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java +++ /dev/null @@ -1,123 +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.log; - -/** - * Stores information about an event that occurred in SystemUI to be used for debugging and triage. - * Every rich event has a time stamp, event type, and log level, with the option to provide the - * reason this event was triggered. - * Events are stored in {@link SysuiLog} and can be printed in a dumpsys. - */ -public abstract class RichEvent extends Event { - private int mType; - - /** - * Initializes a rich event that includes an event type that matches with an index in the array - * getEventLabels(). - */ - public RichEvent init(@Event.Level int logLevel, int type, String reason) { - final int numEvents = getEventLabels().length; - if (type < 0 || type >= numEvents) { - throw new IllegalArgumentException("Unsupported event type. Events only supported" - + " from 0 to " + (numEvents - 1) + ", but given type=" + type); - } - mType = type; - super.init(logLevel, getEventLabels()[mType] + " " + reason); - return this; - } - - /** - * Returns an array of the event labels. The index represents the event type and the - * corresponding String stored at that index is the user-readable representation of that event. - * @return array of user readable events, where the index represents its event type constant - */ - public abstract String[] getEventLabels(); - - @Override - public void recycle() { - super.recycle(); - mType = -1; - } - - public int getType() { - return mType; - } - - /** - * Builder to build a RichEvent. - * @param <B> Log specific builder that is extending this builder - * @param <E> Type of event we'll be building - */ - public abstract static class Builder<B extends Builder<B, E>, E extends RichEvent> { - public static final int UNINITIALIZED = -1; - - public final SysuiLog mLog; - private B mBuilder = getBuilder(); - protected int mType; - protected String mReason; - protected @Level int mLogLevel; - - public Builder(SysuiLog sysuiLog) { - mLog = sysuiLog; - reset(); - } - - /** - * Reset this builder's parameters so it can be reused to build another RichEvent. - */ - public void reset() { - mType = UNINITIALIZED; - mReason = null; - mLogLevel = VERBOSE; - } - - /** - * Get the log-specific builder. - */ - public abstract B getBuilder(); - - /** - * Build the log-specific event given an event to populate. - */ - public abstract E build(E e); - - /** - * Optional - set the log level. Defaults to DEBUG. - */ - public B setLogLevel(@Level int logLevel) { - mLogLevel = logLevel; - return mBuilder; - } - - /** - * Required - set the event type. These events must correspond with the events from - * getEventLabels(). - */ - public B setType(int type) { - mType = type; - return mBuilder; - } - - /** - * Optional - set the reason why this event was triggered. - */ - public B setReason(String reason) { - mReason = reason; - return mBuilder; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java deleted file mode 100644 index 9ee3e6765e4a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java +++ /dev/null @@ -1,180 +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.log; - -import android.os.Build; -import android.os.SystemProperties; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.DumpController; -import com.android.systemui.Dumpable; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.ArrayDeque; -import java.util.Locale; - -/** - * Thread-safe logger in SystemUI which prints logs to logcat and stores logs to be - * printed by the DumpController. This is an alternative to printing directly - * to avoid logs being deleted by chatty. The number of logs retained is varied based on - * whether the build is {@link Build.IS_DEBUGGABLE}. - * - * To manually view the logs via adb: - * adb shell dumpsys activity service com.android.systemui/.SystemUIService \ - * dependency DumpController <SysuiLogId> - * - * Logs can be disabled by setting the following SystemProperty and then restarting the device: - * adb shell setprop persist.sysui.log.enabled.<id> true/false && adb reboot - * - * @param <E> Type of event we'll be logging - */ -public class SysuiLog<E extends Event> implements Dumpable { - public static final SimpleDateFormat DATE_FORMAT = - new SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US); - - protected final Object mDataLock = new Object(); - private final String mId; - private final int mMaxLogs; - protected boolean mEnabled; - protected boolean mLogToLogcatEnabled; - - @VisibleForTesting protected ArrayDeque<E> mTimeline; - - /** - * Creates a SysuiLog - * @param dumpController where to register this logger's dumpsys - * @param id user-readable tag for this logger - * @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true - * @param maxLogs maximum number of logs to retain when {@link sDebuggable} is false - */ - public SysuiLog(DumpController dumpController, String id, int maxDebugLogs, int maxLogs) { - this(dumpController, id, sDebuggable ? maxDebugLogs : maxLogs, - SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED), - SystemProperties.getBoolean(SYSPROP_LOGCAT_ENABLED_PREFIX + id, - DEFAULT_LOGCAT_ENABLED)); - } - - @VisibleForTesting - protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled, - boolean logcatEnabled) { - mId = id; - mMaxLogs = maxLogs; - mEnabled = enabled; - mLogToLogcatEnabled = logcatEnabled; - mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null; - dumpController.registerDumpable(mId, this); - } - - /** - * Logs an event to the timeline which can be printed by the dumpsys. - * May also log to logcat if enabled. - * @return the last event that was discarded from the Timeline (can be recycled) - */ - public E log(E event) { - if (!mEnabled) { - return null; - } - - E recycledEvent = null; - synchronized (mDataLock) { - if (mTimeline.size() >= mMaxLogs) { - recycledEvent = mTimeline.removeFirst(); - } - - mTimeline.add(event); - } - - if (mLogToLogcatEnabled) { - final String strEvent = eventToString(event); - switch (event.getLogLevel()) { - case Event.VERBOSE: - Log.v(mId, strEvent); - break; - case Event.DEBUG: - Log.d(mId, strEvent); - break; - case Event.ERROR: - Log.e(mId, strEvent); - break; - case Event.INFO: - Log.i(mId, strEvent); - break; - case Event.WARN: - Log.w(mId, strEvent); - break; - } - } - - if (recycledEvent != null) { - recycledEvent.recycle(); - } - - return recycledEvent; - } - - /** - * @return user-readable string of the given event with timestamp - */ - private String eventToTimestampedString(Event event) { - StringBuilder sb = new StringBuilder(); - sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp())); - sb.append(" "); - sb.append(event.getMessage()); - return sb.toString(); - } - - /** - * @return user-readable string of the given event without a timestamp - */ - public String eventToString(Event event) { - return event.getMessage(); - } - - @GuardedBy("mDataLock") - private void dumpTimelineLocked(PrintWriter pw) { - pw.println("\tTimeline:"); - - for (Event event : mTimeline) { - pw.println("\t" + eventToTimestampedString(event)); - } - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(mId + ":"); - - if (mEnabled) { - synchronized (mDataLock) { - dumpTimelineLocked(pw); - } - } else { - pw.print(" - Logging disabled."); - } - } - - private static boolean sDebuggable = Build.IS_DEBUGGABLE; - private static final String SYSPROP_ENABLED_PREFIX = "persist.sysui.log.enabled."; - private static final String SYSPROP_LOGCAT_ENABLED_PREFIX = "persist.sysui.log.enabled.logcat."; - private static final boolean DEFAULT_ENABLED = sDebuggable; - private static final boolean DEFAULT_LOGCAT_ENABLED = false; - private static final int DEFAULT_MAX_DEBUG_LOGS = 100; - private static final int DEFAULT_MAX_LOGS = 50; -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index f9b18cf17abe..c7bfc06829b3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -280,7 +280,9 @@ public class PipMenuActivityController { if (mToActivityMessenger != null) { Bundle data = new Bundle(); data.putInt(EXTRA_MENU_STATE, menuState); - data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + if (stackBounds != null) { + data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + } data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds); data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout); data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index eba2e7874574..3ae627d27def 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -19,10 +19,6 @@ package com.android.systemui.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import android.animation.AnimationHandler; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeAnimator; import android.annotation.Nullable; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; @@ -36,6 +32,7 @@ import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.view.Choreographer; import androidx.dynamicanimation.animation.SpringForce; @@ -88,9 +85,11 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call /** PIP's current bounds on the screen. */ private final Rect mBounds = new Rect(); + private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider = + new SfVsyncFrameCallbackProvider(); + /** - * Bounds that are animated using the physics animator. PIP is moved to these bounds whenever - * the {@link #mVsyncTimeAnimator} ticks. + * Bounds that are animated using the physics animator. */ private final Rect mAnimatedBounds = new Rect(); @@ -100,12 +99,16 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance( mAnimatedBounds); + /** Callback that re-sizes PIP to the animated bounds. */ + private final Choreographer.FrameCallback mResizePipVsyncCallback = + l -> resizePipUnchecked(mAnimatedBounds); + /** - * Time animator whose frame timing comes from the SurfaceFlinger vsync frame provider. At each - * frame, PIP is moved to {@link #mAnimatedBounds}, which are animated asynchronously using - * physics animations. + * Update listener that posts a vsync frame callback to resize PIP to {@link #mAnimatedBounds}. */ - private TimeAnimator mVsyncTimeAnimator; + private final PhysicsAnimator.UpdateListener<Rect> mResizePipVsyncUpdateListener = + (target, values) -> + mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; @@ -126,39 +129,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; mFlingAnimationUtils = flingAnimationUtils; - final AnimationHandler vsyncFrameCallbackProvider = new AnimationHandler(); - vsyncFrameCallbackProvider.setProvider(new SfVsyncFrameCallbackProvider()); - onConfigurationChanged(); - - // Construct a time animator that uses the vsync frame provider. Physics animations can't - // use custom frame providers, since they rely on constant time between frames to run the - // physics simulations. To work around this, we physically-animate a second set of bounds, - // and apply those animating bounds to the PIP in-sync via this TimeAnimator. - mVsyncTimeAnimator = new TimeAnimator() { - @Override - public AnimationHandler getAnimationHandler() { - return vsyncFrameCallbackProvider; - } - }; - - // When the time animator ticks, move PIP to the animated bounds. - mVsyncTimeAnimator.setTimeListener( - (animation, totalTime, deltaTime) -> - resizePipUnchecked(mAnimatedBounds)); - - // Add a listener for cancel/end events that moves PIP to the final animated bounds. - mVsyncTimeAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - resizePipUnchecked(mAnimatedBounds); - } - - @Override - public void onAnimationEnd(Animator animation) { - resizePipUnchecked(mAnimatedBounds); - } - }); } /** @@ -429,7 +400,6 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call */ private void cancelAnimations() { mAnimatedBoundsPhysicsAnimator.cancel(); - mVsyncTimeAnimator.cancel(); } /** @@ -457,10 +427,8 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call cancelAnimations(); mAnimatedBoundsPhysicsAnimator - .withEndActions( - mVsyncTimeAnimator::cancel) + .addUpdateListener(mResizePipVsyncUpdateListener) .start(); - mVsyncTimeAnimator.start(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index f66a1ece1868..f710f7fc47e2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -68,7 +68,7 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil override fun updateResources(): Boolean { with(mContext.resources) { smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size) - cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2 + cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin) } requestLayout() diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java index 4c7d82e99d06..5a9c360e3104 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java @@ -30,6 +30,8 @@ import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.DualToneHandler; import com.android.systemui.R; +import java.util.Objects; + public class QSCarrier extends LinearLayout { private View mMobileGroup; @@ -39,6 +41,7 @@ public class QSCarrier extends LinearLayout { private DualToneHandler mDualToneHandler; private ColorStateList mColorForegroundStateList; private float mColorForegroundIntensity; + private QSCarrierGroupController.CellSignalState mLastSignalState; public QSCarrier(Context context) { super(context); @@ -74,6 +77,8 @@ public class QSCarrier extends LinearLayout { } public void updateState(QSCarrierGroupController.CellSignalState state) { + if (Objects.equals(state, mLastSignalState)) return; + mLastSignalState = state; mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE); if (state.visible) { mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java index fb106425ec63..86fccd7f9d52 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java @@ -29,6 +29,7 @@ import android.util.Log; import android.view.View; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.keyguard.CarrierTextController; @@ -37,6 +38,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.policy.NetworkController; +import java.util.Objects; import java.util.function.Consumer; import javax.inject.Inject; @@ -297,6 +299,27 @@ public class QSCarrierGroupController { String contentDescription; String typeContentDescription; boolean roaming; + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof CellSignalState)) return false; + CellSignalState other = (CellSignalState) obj; + return this.visible == other.visible + && this.mobileSignalIconId == other.mobileSignalIconId + && Objects.equals(this.contentDescription, other.contentDescription) + && Objects.equals(this.typeContentDescription, other.typeContentDescription) + && this.roaming == other.roaming; + } + + @Override + public int hashCode() { + return Objects.hash(visible, + mobileSignalIconId, + contentDescription, + typeContentDescription, + roaming); + } } public static class Builder { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt deleted file mode 100644 index 3f0c5bb7f620..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt +++ /dev/null @@ -1,140 +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.qs - -import android.content.ContentResolver -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler -import android.os.Looper -import android.provider.Settings -import android.util.Log -import com.android.systemui.plugins.qs.QSTile - -private const val TAG = "QSColorController" -private const val QS_COLOR_ICON = "qs_color_icon" -private const val QS_COLOR_ENABLED = "qs_color_enabled" -private const val QS_COLOR_OVERRIDDEN_TILES = "qs_color_overridden_tiles" -private val qsColorIconUri = Settings.System.getUriFor(QS_COLOR_ICON) -private val qsColorEnabledUri = Settings.System.getUriFor(QS_COLOR_ENABLED) -class QSColorController private constructor() { - - private var overrideColor = false - private var colorIcon = false - private lateinit var colorCache: SettingBackedMap - - companion object { - val instance = QSColorController() - internal fun overrideColor() = instance.overrideColor - internal fun colorIcon() = instance.colorIcon - - @QSTile.ColorTile - private fun getColorFromSetting(setting: String): Int { - return when (setting.toLowerCase()) { - "red" -> QSTile.COLOR_TILE_RED - "blue" -> QSTile.COLOR_TILE_BLUE - "green" -> QSTile.COLOR_TILE_GREEN - "yellow" -> QSTile.COLOR_TILE_YELLOW - else -> QSTile.COLOR_TILE_ACCENT - } - } - } - - private fun getBooleanSetting(key: String, default: Boolean = false): Boolean = - try { - Settings.System.getInt(contentResolver, key) != 0 - } catch (_: Settings.SettingNotFoundException) { - default - } - - private lateinit var tileHost: QSHost - private lateinit var contentResolver: ContentResolver - - fun initQSTileHost(host: QSHost) { - tileHost = host - contentResolver = tileHost.context.contentResolver - colorCache = SettingBackedMap(contentResolver, mutableMapOf()) - colorIcon = getBooleanSetting(QS_COLOR_ICON) - overrideColor = getBooleanSetting(QS_COLOR_ENABLED) - readExistingSettings() - contentResolver.registerContentObserver(qsColorEnabledUri, true, settingsListener) - contentResolver.registerContentObserver(qsColorIconUri, false, settingsListener) - } - - private fun readExistingSettings() { - Settings.System.getString(contentResolver, QS_COLOR_OVERRIDDEN_TILES)?.split(",") - ?.mapNotNull { spec -> - Settings.System.getString(contentResolver, "$QS_COLOR_ENABLED/$spec")?.let { - spec to it - } - }?.forEach { - modifyTileColor(it.first, getColorFromSetting(it.second)) - } - } - - private val settingsListener = object : ContentObserver(Handler(Looper.getMainLooper())) { - override fun onChange(selfChange: Boolean, uri: Uri) { - super.onChange(selfChange, uri) - when (uri) { - qsColorIconUri -> colorIcon = getBooleanSetting(QS_COLOR_ICON) - qsColorEnabledUri -> overrideColor = getBooleanSetting(QS_COLOR_ENABLED) - else -> { - uri.path?.drop("/system/".length)?.let { - val color = getColorFromSetting( - Settings.System.getString(contentResolver, it) ?: "accent") - val tileSpec = uri.lastPathSegment ?: "" - modifyTileColor(tileSpec, color) - } - } - } - } - } - - private fun modifyTileColor(spec: String, @QSTile.ColorTile color: Int) { - Log.w(TAG, "Setting color of tile $spec to $color") - colorCache.put(spec, color) - tileHost.tiles.firstOrNull { it.tileSpec == spec }?.setColor(color) - } - - fun applyColorToTile(tile: QSTile) { - colorCache.get(tile.tileSpec)?.let { - modifyTileColor(tile.tileSpec, it) - } - } - - fun applyColorToAllTiles() = tileHost.tiles.forEach(::applyColorToTile) - - fun destroy() { - contentResolver.unregisterContentObserver(settingsListener) - } - - class SettingBackedMap( - private val contentResolver: ContentResolver, - private val map: MutableMap<String, Int> - ) : MutableMap<String, @QSTile.ColorTile Int> by map { - override fun put(key: String, @QSTile.ColorTile value: Int): Int? { - return map.put(key, value).also { - Settings.System.putString(contentResolver, QS_COLOR_OVERRIDDEN_TILES, - map.filterValues { it != QSTile.COLOR_TILE_ACCENT } - .keys - .joinToString(",")) - } - } - } -} -fun overrideColor() = QSColorController.overrideColor() -fun colorIcon() = QSColorController.colorIcon()
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 0134aa3a15df..5de6d1c42b4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -169,7 +169,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { v.setText(mContext.getString( com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, + Build.VERSION.RELEASE_OR_CODENAME, Build.ID)); v.setVisibility(View.VISIBLE); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 1077834e7146..6c697184f082 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -36,7 +36,7 @@ import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.text.TextUtils; +import android.os.Handler; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -45,7 +45,6 @@ import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.RemoteViews; import android.widget.TextView; import androidx.core.graphics.drawable.RoundedBitmapDrawable; @@ -72,8 +71,6 @@ public class QSMediaPlayer { private View mSeamless; private MediaSession.Token mToken; private MediaController mController; - private int mWidth; - private int mHeight; private int mForegroundColor; private int mBackgroundColor; private ComponentName mRecvComponent; @@ -158,16 +155,11 @@ public class QSMediaPlayer { * * @param context * @param parent - * @param width - * @param height */ - public QSMediaPlayer(Context context, ViewGroup parent, int width, int height) { + public QSMediaPlayer(Context context, ViewGroup parent) { mContext = context; LayoutInflater inflater = LayoutInflater.from(mContext); mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qs_media_panel, parent, false); - - mWidth = width; - mHeight = height; } public View getView() { @@ -217,27 +209,18 @@ public class QSMediaPlayer { Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif); // Album art - addAlbumArtBackground(mMediaMetadata, bgColor, mWidth, mHeight); + addAlbumArt(mMediaMetadata, bgColor); - // Reuse notification header instead of reimplementing everything - RemoteViews headerRemoteView = builder.makeNotificationHeader(); LinearLayout headerView = mMediaNotifView.findViewById(R.id.header); - View result = headerRemoteView.apply(mContext, headerView); - result.setPadding(0, 0, 0, 0); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, 75); - result.setLayoutParams(lp); - headerView.removeAllViews(); - headerView.addView(result); // App icon - ImageView appIcon = headerView.findViewById(com.android.internal.R.id.icon); + ImageView appIcon = headerView.findViewById(R.id.icon); Drawable iconDrawable = icon.loadDrawable(mContext); iconDrawable.setTint(iconColor); appIcon.setImageDrawable(iconDrawable); // App title - TextView appName = headerView.findViewById(com.android.internal.R.id.app_name_text); + TextView appName = headerView.findViewById(R.id.app_name); String appNameString = builder.loadHeaderAppName(); appName.setText(appNameString); appName.setTextColor(iconColor); @@ -254,25 +237,8 @@ public class QSMediaPlayer { } }); - // Separator - TextView separator = headerView.findViewById(com.android.internal.R.id.header_text_divider); - separator.setTextColor(iconColor); - - // Album name - TextView albumName = headerView.findViewById(com.android.internal.R.id.header_text); - String albumString = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM); - if (TextUtils.isEmpty(albumString)) { - albumName.setVisibility(View.GONE); - separator.setVisibility(View.GONE); - } else { - albumName.setText(albumString); - albumName.setTextColor(iconColor); - albumName.setVisibility(View.VISIBLE); - separator.setVisibility(View.VISIBLE); - } - // Transfer chip - mSeamless = headerView.findViewById(com.android.internal.R.id.media_seamless); + mSeamless = headerView.findViewById(R.id.media_seamless); mSeamless.setVisibility(View.VISIBLE); updateChip(device); ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class); @@ -284,13 +250,13 @@ public class QSMediaPlayer { }); // Artist name - TextView artistText = mMediaNotifView.findViewById(R.id.header_title); + TextView artistText = headerView.findViewById(R.id.header_artist); String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST); artistText.setText(artistName); artistText.setTextColor(iconColor); // Song name - TextView titleText = mMediaNotifView.findViewById(R.id.header_text); + TextView titleText = headerView.findViewById(R.id.header_text); String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); titleText.setText(songName); titleText.setTextColor(iconColor); @@ -363,34 +329,25 @@ public class QSMediaPlayer { return (state.getState() == PlaybackState.STATE_PLAYING); } - private void addAlbumArtBackground(MediaMetadata metadata, int bgColor, int width, int height) { + private void addAlbumArt(MediaMetadata metadata, int bgColor) { Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); + ImageView albumView = mMediaNotifView.findViewById(R.id.album_art); if (albumArt != null) { Log.d(TAG, "updating album art"); Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); - Bitmap scaled = scaleBitmap(original, width, height); - Canvas canvas = new Canvas(scaled); - - // Add translucent layer over album art to improve contrast - Paint p = new Paint(); - p.setStyle(Paint.Style.FILL); - p.setColor(bgColor); - p.setAlpha(200); - canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p); - + int albumSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_album_size); + Bitmap scaled = scaleBitmap(original, albumSize, albumSize); RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create( mContext.getResources(), scaled); roundedDrawable.setCornerRadius(radius); - - mMediaNotifView.setBackground(roundedDrawable); + albumView.setImageDrawable(roundedDrawable); } else { Log.e(TAG, "No album art available"); - GradientDrawable rect = new GradientDrawable(); - rect.setCornerRadius(radius); - rect.setColor(bgColor); - mMediaNotifView.setBackground(rect); + albumView.setImageDrawable(null); } + + mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor)); } private Bitmap scaleBitmap(Bitmap original, int width, int height) { @@ -414,6 +371,13 @@ public class QSMediaPlayer { if (mSeamless == null) { return; } + Handler handler = mSeamless.getHandler(); + handler.post(() -> { + updateChipInternal(device); + }); + } + + private void updateChipInternal(MediaDevice device) { ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor); // Update the outline color @@ -423,8 +387,8 @@ public class QSMediaPlayer { rect.setStroke(2, mForegroundColor); rect.setColor(mBackgroundColor); - ImageView iconView = mSeamless.findViewById(com.android.internal.R.id.media_seamless_image); - TextView deviceName = mSeamless.findViewById(com.android.internal.R.id.media_seamless_text); + ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image); + TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text); deviceName.setTextColor(fgTintList); if (device != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 35b8312ba25c..14a117ca40a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -31,7 +31,6 @@ import android.metrics.LogMaker; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.service.quicksettings.Tile; import android.util.AttributeSet; @@ -62,7 +61,6 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.settings.BrightnessController; import com.android.systemui.settings.ToggleSliderView; import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.statusbar.phone.NPVPluginManager; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener; import com.android.systemui.tuner.TunerService; @@ -97,6 +95,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>(); private LocalMediaManager mLocalMediaManager; private MediaDevice mDevice; + private boolean mUpdateCarousel = false; protected boolean mExpanded; protected boolean mListening; @@ -120,7 +119,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private FrameLayout mPluginFrame; private final PluginManager mPluginManager; - private NPVPluginManager mNPVPluginManager; private final LocalMediaManager.DeviceCallback mDeviceCallback = new LocalMediaManager.DeviceCallback() { @@ -187,7 +185,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from( mContext).inflate(R.layout.media_carousel, this, false); mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel); - addView(mediaScrollView); + addView(mediaScrollView, 0); } else { mMediaCarousel = null; } @@ -201,14 +199,23 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne findViewById(R.id.brightness_slider), broadcastDispatcher); mDumpController = dumpController; mPluginManager = pluginManager; - if (mPluginManager != null && Settings.System.getInt( - mContext.getContentResolver(), "npv_plugin_flag", 0) == 2) { - mPluginFrame = (FrameLayout) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_expanded_plugin_frame, this, false); - addView(mPluginFrame); - mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager); - } + } + @Override + public void onVisibilityAggregated(boolean isVisible) { + super.onVisibilityAggregated(isVisible); + if (!isVisible && mUpdateCarousel) { + for (QSMediaPlayer player : mMediaPlayers) { + if (player.isPlaying()) { + LayoutParams lp = (LayoutParams) player.getView().getLayoutParams(); + mMediaCarousel.removeView(player.getView()); + mMediaCarousel.addView(player.getView(), 0, lp); + ((HorizontalScrollView) mMediaCarousel.getParent()).fullScroll(View.FOCUS_LEFT); + mUpdateCarousel = false; + break; + } + } + } } /** @@ -248,7 +255,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } } - int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height); int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width); int padding = (int) getResources().getDimension(R.dimen.qs_media_padding); LayoutParams lp = new LayoutParams(playerWidth, ViewGroup.LayoutParams.MATCH_PARENT); @@ -257,8 +263,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (player == null) { Log.d(TAG, "creating new player"); - - player = new QSMediaPlayer(mContext, this, playerWidth, playerHeight); + player = new QSMediaPlayer(mContext, this); if (player.isPlaying()) { mMediaCarousel.addView(player.getView(), 0, lp); // add in front @@ -267,9 +272,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } mMediaPlayers.add(player); } else if (player.isPlaying()) { - // move it to the front - mMediaCarousel.removeView(player.getView()); - mMediaCarousel.addView(player.getView(), 0, lp); + mUpdateCarousel = true; } Log.d(TAG, "setting player session"); @@ -556,7 +559,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (mListening) { refreshAllTiles(); } - if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening); } public void setListening(boolean listening, boolean expanded) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 3fcd1c19370f..476af20b78f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -116,6 +116,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic @Override public void onClick(View v) { + if (!hasFooter()) return; mHandler.sendEmptyMessage(H.CLICK); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index f71150fc348c..00e09f8d4725 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -90,8 +90,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private int mCurrentUser; private final Optional<StatusBar> mStatusBarOptional; - private QSColorController mQSColorController = QSColorController.Companion.getInstance(); - @Inject public QSTileHost(Context context, StatusBarIconController iconController, @@ -127,8 +125,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. mAutoTiles = autoTiles.get(); }); - - mQSColorController.initQSTileHost(this); } public StatusBarIconController getIconController() { @@ -142,8 +138,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mServices.destroy(); mPluginManager.removePluginListener(this); mDumpController.unregisterDumpable(this); - - mQSColorController.destroy(); } @Override @@ -280,8 +274,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mCallbacks.get(i).onTilesChanged(); } } - - mQSColorController.applyColorToAllTiles(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index cec1cb2fb53b..9018a375c365 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -23,13 +23,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; import android.media.session.MediaController; @@ -45,9 +39,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import androidx.core.graphics.drawable.RoundedBitmapDrawable; -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; - import com.android.systemui.R; import java.util.List; @@ -63,7 +54,6 @@ public class QuickQSMediaPlayer { private LinearLayout mMediaNotifView; private MediaSession.Token mToken; private MediaController mController; - private int mBackgroundColor; private int mForegroundColor; private ComponentName mRecvComponent; @@ -142,7 +132,6 @@ public class QuickQSMediaPlayer { View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) { mToken = token; mForegroundColor = iconColor; - mBackgroundColor = bgColor; String oldPackage = ""; if (mController != null) { @@ -185,8 +174,7 @@ public class QuickQSMediaPlayer { } }); - // Album art - addAlbumArtBackground(mMediaMetadata, mBackgroundColor); + mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor)); // App icon ImageView appIcon = mMediaNotifView.findViewById(R.id.icon); @@ -194,14 +182,8 @@ public class QuickQSMediaPlayer { iconDrawable.setTint(mForegroundColor); appIcon.setImageDrawable(iconDrawable); - // Artist name - TextView appText = mMediaNotifView.findViewById(R.id.header_title); - String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST); - appText.setText(artistName); - appText.setTextColor(mForegroundColor); - // Song name - TextView titleText = mMediaNotifView.findViewById(R.id.header_text); + TextView titleText = mMediaNotifView.findViewById(R.id.header_title); String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); titleText.setText(songName); titleText.setTextColor(mForegroundColor); @@ -277,54 +259,4 @@ public class QuickQSMediaPlayer { public boolean hasMediaSession() { return mController != null && mController.getPlaybackState() != null; } - - private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) { - Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); - float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); - Rect bounds = new Rect(); - mMediaNotifView.getBoundsOnScreen(bounds); - int width = bounds.width(); - int height = bounds.height(); - if (albumArt != null && width > 0 && height > 0) { - Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); - Bitmap scaled = scaleBitmap(original, width, height); - Canvas canvas = new Canvas(scaled); - - // Add translucent layer over album art to improve contrast - Paint p = new Paint(); - p.setStyle(Paint.Style.FILL); - p.setColor(bgColor); - p.setAlpha(200); - canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p); - - RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create( - mContext.getResources(), scaled); - roundedDrawable.setCornerRadius(radius); - - mMediaNotifView.setBackground(roundedDrawable); - } else { - Log.e(TAG, "No album art available"); - GradientDrawable rect = new GradientDrawable(); - rect.setCornerRadius(radius); - rect.setColor(bgColor); - mMediaNotifView.setBackground(rect); - } - } - - private Bitmap scaleBitmap(Bitmap original, int width, int height) { - Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(cropped); - - float scale = (float) cropped.getWidth() / (float) original.getWidth(); - float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f; - Matrix transformation = new Matrix(); - transformation.postTranslate(0, dy); - transformation.preScale(scale, scale); - - Paint paint = new Paint(); - paint.setFilterBitmap(true); - canvas.drawBitmap(original, transformation, paint); - - return cropped; - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index b05d4fdf7db7..e9207016eb68 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -85,20 +85,21 @@ public class QuickQSPanel extends QSPanel { mHorizontalLinearLayout.setClipChildren(false); mHorizontalLinearLayout.setClipToPadding(false); + int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); + mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout); + LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); + lp2.setMarginEnd(marginSize); + lp2.setMarginStart(0); + mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2); + mTileLayout = new DoubleLineTileLayout(context); mMediaTileLayout = mTileLayout; mRegularTileLayout = new HeaderTileLayout(context); LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); - lp.setMarginEnd(10); - lp.setMarginStart(0); + lp.setMarginEnd(0); + lp.setMarginStart(marginSize); mHorizontalLinearLayout.addView((View) mTileLayout, lp); - mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout); - LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); - lp2.setMarginEnd(0); - lp2.setMarginStart(25); - mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2); - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); mTileLayout.setListening(mListening); diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 37743ec55517..9f59277c918a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -1,5 +1,7 @@ package com.android.systemui.qs; +import static com.android.systemui.util.Utils.useQsMediaPlayer; + import android.content.Context; import android.content.res.Resources; import android.provider.Settings; @@ -42,7 +44,8 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public TileLayout(Context context, AttributeSet attrs) { super(context, attrs); setFocusableInTouchMode(true); - mLessRows = Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0; + mLessRows = (Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0) + || useQsMediaPlayer(context); updateResources(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 557c64b7dfb9..ae6162219afa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -83,7 +83,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile = new Tile(); updateDefaultTileAndIcon(); mServiceManager = host.getTileServices().getTileWrapper(this); - if (mServiceManager.isBooleanTile()) { + if (mServiceManager.isToggleableTile()) { // Replace states with BooleanState resetStates(); } @@ -191,7 +191,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile.setLabel(tile.getLabel()); mTile.setSubtitle(tile.getSubtitle()); mTile.setContentDescription(tile.getContentDescription()); - mTile.setStateDescription(tile.getStateDescription()); mTile.setState(tile.getState()); } @@ -253,7 +252,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Override public State newTileState() { - if (mServiceManager != null && mServiceManager.isBooleanTile()) { + if (mServiceManager != null && mServiceManager.isToggleableTile()) { return new BooleanState(); } return new State(); @@ -346,12 +345,6 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener state.contentDescription = state.label; } - if (mTile.getStateDescription() != null) { - state.stateDescription = mTile.getStateDescription(); - } else { - state.stateDescription = null; - } - if (state instanceof BooleanState) { state.expandedAccessibilityClassName = Switch.class.getName(); ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index ad79cadcc12d..17b0251837e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -141,16 +141,16 @@ public class TileLifecycleManager extends BroadcastReceiver implements /** * Determines whether the associated TileService is a Boolean Tile. * - * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this + * @return true if {@link TileService#META_DATA_TOGGLEABLE_TILE} is set to {@code true} for this * tile - * @see TileService#META_DATA_BOOLEAN_TILE + * @see TileService#META_DATA_TOGGLEABLE_TILE */ - public boolean isBooleanTile() { + public boolean isToggleableTile() { try { ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA); return info.metaData != null - && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false); + && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false); } catch (PackageManager.NameNotFoundException e) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 1902d655abc3..cfa8fb6373a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -124,8 +124,8 @@ public class TileServiceManager { return mStateManager.isActiveTile(); } - public boolean isBooleanTile() { - return mStateManager.isBooleanTile(); + public boolean isToggleableTile() { + return mStateManager.isToggleableTile(); } public void setShowingDialog(boolean dialog) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 2b53727f237e..554672d88052 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -16,7 +16,6 @@ package com.android.systemui.qs.tileimpl; import android.content.Context; import android.os.Build; -import android.provider.Settings; import android.util.Log; import android.view.ContextThemeWrapper; @@ -33,7 +32,6 @@ import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; -import com.android.systemui.qs.tiles.ControlsTile; import com.android.systemui.qs.tiles.DataSaverTile; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.FlashlightTile; @@ -60,7 +58,6 @@ public class QSFactoryImpl implements QSFactory { private final Provider<WifiTile> mWifiTileProvider; private final Provider<BluetoothTile> mBluetoothTileProvider; - private final Provider<ControlsTile> mControlsTileProvider; private final Provider<CellularTile> mCellularTileProvider; private final Provider<DndTile> mDndTileProvider; private final Provider<ColorInversionTile> mColorInversionTileProvider; @@ -85,7 +82,6 @@ public class QSFactoryImpl implements QSFactory { @Inject public QSFactoryImpl(Provider<WifiTile> wifiTileProvider, Provider<BluetoothTile> bluetoothTileProvider, - Provider<ControlsTile> controlsTileProvider, Provider<CellularTile> cellularTileProvider, Provider<DndTile> dndTileProvider, Provider<ColorInversionTile> colorInversionTileProvider, @@ -106,7 +102,6 @@ public class QSFactoryImpl implements QSFactory { Provider<ScreenRecordTile> screenRecordTileProvider) { mWifiTileProvider = wifiTileProvider; mBluetoothTileProvider = bluetoothTileProvider; - mControlsTileProvider = controlsTileProvider; mCellularTileProvider = cellularTileProvider; mDndTileProvider = dndTileProvider; mColorInversionTileProvider = colorInversionTileProvider; @@ -146,11 +141,6 @@ public class QSFactoryImpl implements QSFactory { return mWifiTileProvider.get(); case "bt": return mBluetoothTileProvider.get(); - case "controls": - if (Settings.System.getInt(mHost.getContext().getContentResolver(), - "npv_plugin_flag", 0) == 3) { - return mControlsTileProvider.get(); - } else return null; case "cell": return mCellularTileProvider.get(); case "dnd": diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index 88c7964144c1..31526bf8f5e1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -14,12 +14,10 @@ package com.android.systemui.qs.tileimpl; -import static com.android.systemui.qs.QSColorControllerKt.colorIcon; import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.ColorStateList; @@ -150,15 +148,10 @@ public class QSIconViewImpl extends QSIconView { iv.clearColorFilter(); } if (state.state != mState) { - int color = getColor(state.state, state.colorActive); + int color = getColor(state.state); mState = state.state; if (mTint != 0 && allowAnimations && shouldAnimate(iv)) { - if (colorIcon()) { - animateColor(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); - } else { - animateGrayScale(mTint, color, iv, - () -> updateIcon(iv, state, allowAnimations)); - } + animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations)); mTint = color; } else { if (iv instanceof AlphaControlledSlashImageView) { @@ -175,12 +168,8 @@ public class QSIconViewImpl extends QSIconView { } } - protected int getColor(int state, int colorActive) { - return getColorForState(getContext(), state, colorActive); - } - protected int getColor(int state) { - return getColor(state, -1); + return getColorForState(getContext(), state); } private void animateGrayScale(int fromColor, int toColor, ImageView iv, @@ -217,37 +206,6 @@ public class QSIconViewImpl extends QSIconView { } } - private void animateColor(int fromColor, int toColor, ImageView iv, - final Runnable endRunnable) { - if (iv instanceof AlphaControlledSlashImageView) { - ((AlphaControlledSlashImageView) iv) - .setFinalImageTintList(ColorStateList.valueOf(toColor)); - } - if (mAnimationEnabled && ValueAnimator.areAnimatorsEnabled()) { - - - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - anim.setDuration(QS_ANIM_LENGTH); - anim.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - int color = (int) ArgbEvaluator.getInstance().evaluate(fraction, fromColor, - toColor); - - setTint(iv, color); - }); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - endRunnable.run(); - } - }); - anim.start(); - } else { - setTint(iv, toColor); - endRunnable.run(); - } - } - public static void setTint(ImageView iv, int color) { iv.setImageTintList(ColorStateList.valueOf(color)); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index fda9e5b1f1ef..2fe64d26f3ac 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -13,8 +13,6 @@ */ package com.android.systemui.qs.tileimpl; -import static com.android.systemui.qs.QSColorControllerKt.colorIcon; -import static com.android.systemui.qs.QSColorControllerKt.overrideColor; import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH; import android.animation.ValueAnimator; @@ -65,6 +63,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private String mAccessibilityClass; private boolean mTileState; private boolean mCollapsedView; + private boolean mClicked; private boolean mShowRippleEffect = true; private final ImageView mBg; @@ -75,8 +74,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private int mCircleColor; private int mBgSize; - private final boolean mQsColors = overrideColor(); - private final boolean mQSIcons = colorIcon(); public QSTileBaseView(Context context, QSIconView icon) { this(context, icon, false); @@ -211,7 +208,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } protected void handleStateChanged(QSTile.State state) { - int circleColor = getCircleColor(state.state, mQsColors ? state.colorActive : -1); + int circleColor = getCircleColor(state.state); boolean allowAnimations = animationsEnabled(); if (circleColor != mCircleColor) { if (allowAnimations) { @@ -233,35 +230,13 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setLongClickable(state.handlesLongClick); mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); - final StringBuilder stateDescription = new StringBuilder(); - switch (state.state) { - case Tile.STATE_UNAVAILABLE: - stateDescription.append(mContext.getString(R.string.tile_unavailable)); - break; - case Tile.STATE_INACTIVE: - if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_off)); - } - break; - case Tile.STATE_ACTIVE: - if (state instanceof QSTile.BooleanState) { - stateDescription.append(mContext.getString(R.string.switch_bar_on)); - } - break; - default: - break; - } - if (!TextUtils.isEmpty(state.stateDescription)) { - stateDescription.append(", "); - stateDescription.append(state.stateDescription); - } - setStateDescription(stateDescription.toString()); mAccessibilityClass = state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName; if (state instanceof QSTile.BooleanState) { boolean newState = ((BooleanState) state).value; if (mTileState != newState) { + mClicked = false; mTileState = newState; } } @@ -280,11 +255,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { return mLocInScreen[1] >= -getHeight(); } - private int getCircleColor(int state, int colorActive) { + private int getCircleColor(int state) { switch (state) { case Tile.STATE_ACTIVE: - int color = (colorActive == -1) ? mColorActive : colorActive; - return mQsColors && mQSIcons ? Utils.applyAlpha(0.5f, color) : color; + return mColorActive; case Tile.STATE_INACTIVE: case Tile.STATE_UNAVAILABLE: return mColorDisabled; @@ -294,10 +268,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } } - private int getCircleColor(int state) { - return getCircleColor(state, -1); - } - @Override public void setClickable(boolean clickable) { super.setClickable(clickable); @@ -318,10 +288,23 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } @Override + public boolean performClick() { + mClicked = true; + return super.performClick(); + } + + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); if (!TextUtils.isEmpty(mAccessibilityClass)) { event.setClassName(mAccessibilityClass); + if (Switch.class.getName().equals(mAccessibilityClass)) { + boolean b = mClicked ? !mTileState : mTileState; + String label = getResources() + .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); + event.setContentDescription(label); + event.setChecked(b); + } } } @@ -333,6 +316,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { if (!TextUtils.isEmpty(mAccessibilityClass)) { info.setClassName(mAccessibilityClass); if (Switch.class.getName().equals(mAccessibilityClass)) { + boolean b = mClicked ? !mTileState : mTileState; + String label = getResources() + .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); + info.setText(label); + info.setChecked(b); info.setCheckable(true); if (isLongClickable()) { info.addAction( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 1d379110325a..e559694df375 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -26,7 +26,6 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import static com.android.systemui.qs.QSColorControllerKt.colorIcon; import android.app.ActivityManager; import android.content.Context; @@ -55,7 +54,6 @@ import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; -import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSIconView; @@ -111,10 +109,31 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + /** + * Provides a new {@link TState} of the appropriate type to use between this tile and the + * corresponding view. + * + * @return new state to use by the tile. + */ public abstract TState newTileState(); + /** + * Handles clicks by the user. + * + * Calls to the controller should be made here to set the new state of the device. + */ abstract protected void handleClick(); + /** + * Update state of the tile based on device state + * + * Called whenever the state of the tile needs to be updated, either after user + * interaction or from callbacks from the controller. It populates {@code state} with the + * information to display to the user. + * + * @param state {@link TState} to populate with information to display + * @param arg additional arguments needed to populate {@code state} + */ abstract protected void handleUpdateState(TState state, Object arg); /** @@ -179,6 +198,12 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy return mHost; } + /** + * Return the {@link QSIconView} to be used by this tile's view. + * + * @param context view context for the view + * @return icon view for this tile + */ public QSIconView createTileView(Context context) { return new QSIconViewImpl(context); } @@ -300,11 +325,20 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mCallbacks.clear(); } + /** + * Handles secondary click on the tile. + * + * Defaults to {@link QSTileImpl#handleClick} + */ protected void handleSecondaryClick() { // Default to normal click. handleClick(); } + /** + * Handles long click on the tile by launching the {@link Intent} defined in + * {@link QSTileImpl#getLongClickIntent} + */ protected void handleLongClick() { if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) { showDetail(true); @@ -314,6 +348,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy getLongClickIntent(), 0); } + /** + * Returns an intent to be launched when the tile is long pressed. + * + * @return the intent to launch + */ public abstract Intent getLongClickIntent(); protected void handleRefreshState(Object arg) { @@ -429,13 +468,13 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } } + /** + * Provides a default label for the tile. + * @return default label for the tile. + */ public abstract CharSequence getTileLabel(); public static int getColorForState(Context context, int state) { - return getColorForState(context, state, -1); - } - - public static int getColorForState(Context context, int state, int colorActive) { switch (state) { case Tile.STATE_UNAVAILABLE: return Utils.getDisabled(context, @@ -443,25 +482,13 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy case Tile.STATE_INACTIVE: return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary); case Tile.STATE_ACTIVE: - return getActiveColor(context, colorActive); + return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary); default: Log.e("QSTile", "Invalid state " + state); return 0; } } - private static int getActiveColor(Context context, int colorActive) { - if (colorIcon()) { - if (colorActive == -1) { - return Utils.getColorAccentDefaultColor(context); - } else { - return colorActive; - } - } else { - return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary); - } - } - protected final class H extends Handler { private static final int ADD_CALLBACK = 1; private static final int CLICK = 2; @@ -645,27 +672,4 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy pw.println(this.getClass().getSimpleName() + ":"); pw.print(" "); pw.println(getState().toString()); } - - @Override - public void setColor(@ColorTile int color) { - int resId; - switch(color) { - case COLOR_TILE_RED: - resId = R.color.GM2_red_500; - break; - case COLOR_TILE_BLUE: - resId = R.color.GM2_blue_500; - break; - case COLOR_TILE_GREEN: - resId = R.color.GM2_green_500; - break; - case COLOR_TILE_YELLOW: - resId = R.color.GM2_yellow_500; - break; - default: - resId = -1; - } - mTmpState.colorActive = resId == -1 ? -1 : mContext.getColor(resId); - refreshState(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 361b6c1b1260..9282a2e3b312 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -134,27 +134,25 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { state.label = mContext.getString(R.string.quick_settings_bluetooth_label); state.secondaryLabel = TextUtils.emptyIfNull( getSecondaryLabel(enabled, connecting, connected, state.isTransient)); - state.contentDescription = state.label; - state.stateDescription = ""; if (enabled) { if (connected) { state.icon = new BluetoothConnectedTileIcon(); if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) { state.label = mController.getConnectedDeviceName(); } - state.stateDescription = + state.contentDescription = mContext.getString(R.string.accessibility_bluetooth_name, state.label) + ", " + state.secondaryLabel; } else if (state.isTransient) { state.icon = ResourceIcon.get( com.android.internal.R.drawable.ic_bluetooth_transient_animation); - state.stateDescription = state.secondaryLabel; + state.contentDescription = state.secondaryLabel; } else { state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth); state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_bluetooth); - state.stateDescription = mContext.getString(R.string.accessibility_not_connected); + R.string.accessibility_quick_settings_bluetooth) + "," + + mContext.getString(R.string.accessibility_not_connected); } state.state = Tile.STATE_ACTIVE; } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 58de0575fa75..32b051e35604 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -183,7 +183,6 @@ public class CastTile extends QSTileImpl<BooleanState> { protected void handleUpdateState(BooleanState state, Object arg) { state.label = mContext.getString(R.string.quick_settings_cast_title); state.contentDescription = state.label; - state.stateDescription = ""; state.value = false; final List<CastDevice> devices = mController.getCastDevices(); boolean connecting = false; @@ -193,9 +192,8 @@ public class CastTile extends QSTileImpl<BooleanState> { if (device.state == CastDevice.STATE_CONNECTED) { state.value = true; state.secondaryLabel = getDeviceName(device); - state.stateDescription = state.stateDescription + "," - + mContext.getString( - R.string.accessibility_cast_name, state.label); + state.contentDescription = state.contentDescription + "," + + mContext.getString(R.string.accessibility_cast_name, state.label); connecting = false; break; } else if (device.state == CastDevice.STATE_CONNECTING) { @@ -219,8 +217,9 @@ public class CastTile extends QSTileImpl<BooleanState> { state.state = Tile.STATE_UNAVAILABLE; String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); state.secondaryLabel = noWifi; + state.contentDescription = state.contentDescription + ", " + mContext.getString( + R.string.accessibility_quick_settings_not_available, noWifi); } - state.stateDescription = state.stateDescription + ", " + state.secondaryLabel; mDetailAdapter.updateItems(devices); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index d5f86c951407..22470c7f5af5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -194,13 +194,17 @@ public class CellularTile extends QSTileImpl<SignalState> { state.secondaryLabel = r.getString(R.string.cell_data_off); } - state.contentDescription = state.label; + + // TODO(b/77881974): Instead of switching out the description via a string check for + // we need to have two strings provided by the MobileIconGroup. + final CharSequence contentDescriptionSuffix; if (state.state == Tile.STATE_INACTIVE) { - // This information is appended later by converting the Tile.STATE_INACTIVE state. - state.stateDescription = ""; + contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description); } else { - state.stateDescription = state.secondaryLabel; + contentDescriptionSuffix = state.secondaryLabel; } + + state.contentDescription = state.label + ", " + contentDescriptionSuffix; } private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java deleted file mode 100644 index 39ae66e7607a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2014 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.qs.tiles; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import com.android.systemui.R; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.NPVPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.qs.DetailAdapter; -import com.android.systemui.plugins.qs.QSTile.BooleanState; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.shared.plugins.PluginManager; - -import javax.inject.Inject; - - -/** - * Temporary control test for prototyping - */ -public class ControlsTile extends QSTileImpl<BooleanState> { - private ControlsDetailAdapter mDetailAdapter; - private final ActivityStarter mActivityStarter; - private PluginManager mPluginManager; - private NPVPlugin mPlugin; - private Intent mHomeAppIntent; - - @Inject - public ControlsTile(QSHost host, - ActivityStarter activityStarter, - PluginManager pluginManager) { - super(host); - mActivityStarter = activityStarter; - mPluginManager = pluginManager; - mDetailAdapter = (ControlsDetailAdapter) createDetailAdapter(); - - mHomeAppIntent = new Intent(Intent.ACTION_VIEW); - mHomeAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mHomeAppIntent.setComponent(new ComponentName("com.google.android.apps.chromecast.app", - "com.google.android.apps.chromecast.app.DiscoveryActivity")); - } - - @Override - public DetailAdapter getDetailAdapter() { - return mDetailAdapter; - } - - @Override - public BooleanState newTileState() { - return new BooleanState(); - } - - @Override - public void handleSetListening(boolean listening) { - - } - - @Override - public void setDetailListening(boolean listening) { - if (mPlugin == null) return; - - mPlugin.setListening(listening); - } - - @Override - protected void handleClick() { - showDetail(true); - } - - @Override - public Intent getLongClickIntent() { - return mHomeAppIntent; - } - - @Override - protected void handleSecondaryClick() { - showDetail(true); - } - - @Override - public CharSequence getTileLabel() { - return "Controls"; - } - - @Override - protected void handleUpdateState(BooleanState state, Object arg) { - state.icon = ResourceIcon.get(R.drawable.ic_lightbulb_outline_gm2_24px); - state.label = "Controls"; - } - - @Override - public boolean supportsDetailView() { - return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK; - } - - @Override - public int getMetricsCategory() { - return -1; - } - - @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return "On"; - } else { - return "Off"; - } - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - protected DetailAdapter createDetailAdapter() { - mDetailAdapter = new ControlsDetailAdapter(); - return mDetailAdapter; - } - - private class ControlsDetailAdapter implements DetailAdapter { - private View mDetailView; - protected FrameLayout mHomeControlsLayout; - - public CharSequence getTitle() { - return "Controls"; - } - - public Boolean getToggleState() { - return null; - } - - public boolean getToggleEnabled() { - return false; - } - - public View createDetailView(Context context, View convertView, final ViewGroup parent) { - if (convertView != null) return convertView; - - mHomeControlsLayout = (FrameLayout) LayoutInflater.from(context).inflate( - R.layout.home_controls, parent, false); - mHomeControlsLayout.setVisibility(View.VISIBLE); - parent.addView(mHomeControlsLayout); - - mPluginManager.addPluginListener( - new PluginListener<NPVPlugin>() { - @Override - public void onPluginConnected(NPVPlugin plugin, - Context pluginContext) { - mPlugin = plugin; - mPlugin.attachToRoot(mHomeControlsLayout); - mPlugin.setListening(true); - } - - @Override - public void onPluginDisconnected(NPVPlugin plugin) { - mPlugin.setListening(false); - mHomeControlsLayout.removeAllViews(); - - } - }, NPVPlugin.class, false); - return mHomeControlsLayout; - } - - public Intent getSettingsIntent() { - return mHomeAppIntent; - } - - public void setToggleState(boolean state) { - - } - - public int getMetricsCategory() { - return -1; - } - - public boolean hasHeader() { - return false; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 9215da4cda9a..52d1a5b3b991 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -240,8 +240,6 @@ public class DndTile extends QSTileImpl<BooleanState> { zen != Global.ZEN_MODE_OFF, mController.getConfig(), false)); state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); - // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier - // to understand. switch (zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: state.contentDescription = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index 792c36477962..dafdd89ee62c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -102,13 +102,14 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); state.secondaryLabel = ""; - state.stateDescription = ""; if (!mFlashlightController.isAvailable()) { state.icon = mIcon; state.slash.isSlashed = true; state.secondaryLabel = mContext.getString( R.string.quick_settings_flashlight_camera_in_use); - state.stateDescription = state.secondaryLabel; + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_flashlight_unavailable) + + ", " + state.secondaryLabel; state.state = Tile.STATE_UNAVAILABLE; return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index fd6b936d71c0..42f80109e045 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Intent; import android.os.UserManager; import android.service.quicksettings.Tile; +import android.util.Log; import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -147,7 +148,6 @@ public class HotspotTile extends QSTileImpl<BooleanState> { state.secondaryLabel = getSecondaryLabel( isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices); - state.stateDescription = state.secondaryLabel; } @Nullable @@ -201,6 +201,14 @@ public class HotspotTile extends QSTileImpl<BooleanState> { mCallbackInfo.numConnectedDevices = numDevices; refreshState(mCallbackInfo); } + + @Override + public void onHotspotAvailabilityChanged(boolean available) { + if (!available) { + Log.d(TAG, "Tile removed. Hotspot no longer available"); + mHost.removeTile(getTileSpec()); + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index e617867eb10e..fbdca3ba1c7b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -105,8 +105,15 @@ public class LocationTile extends QSTileImpl<BooleanState> { } state.icon = mIcon; state.slash.isSlashed = !state.value; - state.label = mContext.getString(R.string.quick_settings_location_label); - state.contentDescription = state.label; + if (locationEnabled) { + state.label = mContext.getString(R.string.quick_settings_location_label); + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_location_on); + } else { + state.label = mContext.getString(R.string.quick_settings_location_label); + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_location_off); + } state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.expandedAccessibilityClassName = Switch.class.getName(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 6e8dcf36bacc..b7ce101cacab 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -195,7 +195,6 @@ public class WifiTile extends QSTileImpl<SignalState> { state.activityIn = cb.enabled && cb.activityIn; state.activityOut = cb.enabled && cb.activityOut; final StringBuffer minimalContentDescription = new StringBuffer(); - final StringBuffer minimalStateDescription = new StringBuffer(); final Resources r = mContext.getResources(); if (isTransient) { state.icon = ResourceIcon.get( @@ -220,14 +219,13 @@ public class WifiTile extends QSTileImpl<SignalState> { mContext.getString(R.string.quick_settings_wifi_label)).append(","); if (state.value) { if (wifiConnected) { - minimalStateDescription.append(cb.wifiSignalContentDescription); + minimalContentDescription.append(cb.wifiSignalContentDescription).append(","); minimalContentDescription.append(removeDoubleQuotes(cb.ssid)); if (!TextUtils.isEmpty(state.secondaryLabel)) { minimalContentDescription.append(",").append(state.secondaryLabel); } } } - state.stateDescription = minimalStateDescription.toString(); state.contentDescription = minimalContentDescription.toString(); state.dualLabelContentDescription = r.getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index e54ee51fb9d4..7853dc388bcb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -103,11 +103,14 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements state.icon = mIcon; if (state.value) { state.slash.isSlashed = false; + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_work_mode_on); } else { state.slash.isSlashed = true; + state.contentDescription = mContext.getString( + R.string.accessibility_quick_settings_work_mode_off); } state.label = mContext.getString(R.string.quick_settings_work_mode_label); - state.contentDescription = state.label; state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java index de8e6ea4a0cb..7ebebaaef122 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java +++ b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java @@ -70,6 +70,6 @@ public class TriangleShape extends PathShape { @Override public void getOutline(@NonNull Outline outline) { - outline.setConvexPath(mTriangularPath); + outline.setPath(mTriangularPath); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index b091ad8c0db8..626f298055a0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -16,7 +16,6 @@ package com.android.systemui.screenrecord; -import android.app.Activity; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -26,16 +25,21 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.MediaRecorder; +import android.media.projection.IMediaProjection; +import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.MediaStore; import android.provider.Settings; import android.util.DisplayMetrics; @@ -83,7 +87,6 @@ public class RecordingService extends Service { private static final int AUDIO_SAMPLE_RATE = 44100; private final RecordingController mController; - private MediaProjectionManager mMediaProjectionManager; private MediaProjection mMediaProjection; private Surface mInputSurface; private VirtualDisplay mVirtualDisplay; @@ -134,13 +137,30 @@ public class RecordingService extends Service { switch (action) { case ACTION_START: - int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED); mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false); mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false); - Intent data = intent.getParcelableExtra(EXTRA_DATA); - if (data != null) { - mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data); + try { + IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE); + IMediaProjectionManager mediaService = + IMediaProjectionManager.Stub.asInterface(b); + IMediaProjection proj = mediaService.createProjection(getUserId(), + getPackageName(), + MediaProjectionManager.TYPE_SCREEN_CAPTURE, false); + IBinder projection = proj.asBinder(); + if (projection == null) { + Log.e(TAG, "Projection was null"); + Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG) + .show(); + return Service.START_NOT_STICKY; + } + mMediaProjection = new MediaProjection(getApplicationContext(), + IMediaProjection.Stub.asInterface(projection)); startRecording(); + } catch (RemoteException e) { + e.printStackTrace(); + Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG) + .show(); + return Service.START_NOT_STICKY; } break; @@ -195,9 +215,6 @@ public class RecordingService extends Service { @Override public void onCreate() { super.onCreate(); - - mMediaProjectionManager = - (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); } /** @@ -269,6 +286,7 @@ public class RecordingService extends Service { } private void createRecordingNotification() { + Resources res = getResources(); NotificationChannel channel = new NotificationChannel( CHANNEL_ID, getString(R.string.screenrecord_name), @@ -281,11 +299,15 @@ public class RecordingService extends Service { Bundle extras = new Bundle(); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getResources().getString(R.string.screenrecord_name)); + res.getString(R.string.screenrecord_name)); + + String notificationTitle = mUseAudio + ? res.getString(R.string.screenrecord_ongoing_screen_and_audio) + : res.getString(R.string.screenrecord_ongoing_screen_only); mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_name)) + .setContentTitle(notificationTitle) .setContentText(getResources().getString(R.string.screenrecord_stop_text)) .setUsesChronometer(true) .setColorized(true) @@ -332,8 +354,7 @@ public class RecordingService extends Service { Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) - .setContentTitle(getResources().getString(R.string.screenrecord_name)) - .setContentText(getResources().getString(R.string.screenrecord_save_message)) + .setContentTitle(getResources().getString(R.string.screenrecord_save_message)) .setContentIntent(PendingIntent.getActivity( this, REQUEST_CODE, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index 8324986123bd..566f12b42795 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -16,15 +16,14 @@ package com.android.systemui.screenrecord; -import android.Manifest; import android.app.Activity; import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.media.projection.MediaProjectionManager; import android.os.Bundle; -import android.widget.Toast; +import android.view.Gravity; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.Switch; import com.android.systemui.R; @@ -34,15 +33,11 @@ import javax.inject.Inject; * Activity to select screen recording options */ public class ScreenRecordDialog extends Activity { - private static final int REQUEST_CODE_VIDEO_ONLY = 200; - private static final int REQUEST_CODE_VIDEO_TAPS = 201; - private static final int REQUEST_CODE_PERMISSIONS = 299; - private static final int REQUEST_CODE_VIDEO_AUDIO = 300; - private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301; - private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399; private static final long DELAY_MS = 3000; private final RecordingController mController; + private Switch mAudioSwitch; + private Switch mTapsSwitch; @Inject public ScreenRecordDialog(RecordingController controller) { @@ -52,81 +47,42 @@ public class ScreenRecordDialog extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestScreenCapture(); - } - private void requestScreenCapture() { - MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService( - Context.MEDIA_PROJECTION_SERVICE); - Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent(); + Window window = getWindow(); + // Inflate the decor view, so the attributes below are not overwritten by the theme. + window.getDecorView(); + window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + window.setGravity(Gravity.TOP); + + setContentView(R.layout.screen_record_dialog); + + Button cancelBtn = findViewById(R.id.button_cancel); + cancelBtn.setOnClickListener(v -> { + finish(); + }); - // TODO get saved settings - boolean useAudio = false; - boolean showTaps = false; - if (useAudio) { - startActivityForResult(permissionIntent, - showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO); - } else { - startActivityForResult(permissionIntent, - showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY); - } + Button startBtn = findViewById(R.id.button_start); + startBtn.setOnClickListener(v -> { + requestScreenCapture(); + finish(); + }); + + mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); + mTapsSwitch = findViewById(R.id.screenrecord_taps_switch); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS - || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); - boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO - || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS); - switch (requestCode) { - case REQUEST_CODE_VIDEO_TAPS: - case REQUEST_CODE_VIDEO_AUDIO_TAPS: - case REQUEST_CODE_VIDEO_ONLY: - case REQUEST_CODE_VIDEO_AUDIO: - if (resultCode == RESULT_OK) { - PendingIntent startIntent = PendingIntent.getForegroundService( - this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent( - ScreenRecordDialog.this, resultCode, data, useAudio, - showTaps), - PendingIntent.FLAG_UPDATE_CURRENT - ); - PendingIntent stopIntent = PendingIntent.getService( - this, RecordingService.REQUEST_CODE, - RecordingService.getStopIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - mController.startCountdown(DELAY_MS, startIntent, stopIntent); - } else { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - } - finish(); - break; - case REQUEST_CODE_PERMISSIONS: - int permission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (permission != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - finish(); - } else { - requestScreenCapture(); - } - break; - case REQUEST_CODE_PERMISSIONS_AUDIO: - int videoPermission = checkSelfPermission( - Manifest.permission.WRITE_EXTERNAL_STORAGE); - int audioPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO); - if (videoPermission != PackageManager.PERMISSION_GRANTED - || audioPermission != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, - getResources().getString(R.string.screenrecord_permission_error), - Toast.LENGTH_SHORT).show(); - finish(); - } else { - requestScreenCapture(); - } - break; - } + private void requestScreenCapture() { + boolean useAudio = mAudioSwitch.isChecked(); + boolean showTaps = mTapsSwitch.isChecked(); + PendingIntent startIntent = PendingIntent.getForegroundService(this, + RecordingService.REQUEST_CODE, + RecordingService.getStartIntent( + ScreenRecordDialog.this, RESULT_OK, null, useAudio, showTaps), + PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent stopIntent = PendingIntent.getService(this, + RecordingService.REQUEST_CODE, + RecordingService.getStopIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT); + mController.startCountdown(DELAY_MS, startIntent, stopIntent); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 880b8f8776e8..4f38a15824a4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -69,6 +69,7 @@ import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.animation.Interpolator; +import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; @@ -143,7 +144,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final float BACKGROUND_ALPHA = 0.5f; private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f; private static final float ROUNDED_CORNER_RADIUS = .05f; - private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000; + private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000; private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; @@ -162,6 +163,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private final HorizontalScrollView mActionsContainer; private final LinearLayout mActionsView; private final ImageView mBackgroundProtection; + private final FrameLayout mDismissButton; private Bitmap mScreenBitmap; private AnimatorSet mScreenshotAnimation; @@ -170,6 +172,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private float mScreenshotOffsetYPx; private float mScreenshotHeightPx; private float mCornerScale; + private float mDismissButtonSize; private AsyncTask<Void, Void, Void> mSaveInBgTask; @@ -216,19 +219,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions); mBackgroundProtection = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_background); + mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); + mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button")); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); mScreenshotLayout.setFocusable(true); mScreenshotSelectorView.setFocusable(true); mScreenshotSelectorView.setFocusableInTouchMode(true); - mScreenshotLayout.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { - clearScreenshot("tap_outside"); - } - // Intercept and ignore all touch events - return true; - }); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( @@ -254,6 +252,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y); mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale) / (float) mDisplayMetrics.widthPixels; + mDismissButtonSize = resources.getDimensionPixelSize( + R.dimen.screenshot_dismiss_button_tappable_size); // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); @@ -271,6 +271,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Rect actionsRect = new Rect(); mActionsContainer.getBoundsOnScreen(actionsRect); touchRegion.op(actionsRect, Region.Op.UNION); + Rect dismissRect = new Rect(); + mDismissButton.getBoundsOnScreen(dismissRect); + touchRegion.op(dismissRect, Region.Op.UNION); inoutInfo.touchableRegion.set(touchRegion); } @@ -408,6 +411,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsContainer.setVisibility(View.GONE); mBackgroundView.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); + mDismissButton.setVisibility(View.GONE); mScreenshotView.setVisibility(View.GONE); mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); } @@ -615,6 +619,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotView.setTranslationX(t * finalPos.x); mScreenshotView.setTranslationY(t * finalPos.y); }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + Rect bounds = new Rect(); + mScreenshotView.getBoundsOnScreen(bounds); + mDismissButton.setX(bounds.right - mDismissButtonSize / 2f); + mDismissButton.setY(bounds.top - mDismissButtonSize / 2f); + mDismissButton.setVisibility(View.VISIBLE); + } + }); return anim; } @@ -686,14 +701,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mBackgroundProtection.setAlpha(t); mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t); }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mScreenshotView.requestFocus(); - mScreenshotView.setElevation(50); - } - }); return animator; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index e6082dddd6c7..e7e1ba8c28b4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -106,7 +106,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Initialize screenshot notification smart actions provider. mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, false); + SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); if (mSmartActionsEnabled) { mSmartActionsProvider = SystemUIFactory.getInstance() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java index 22fd37ceaebd..eb580c450730 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java @@ -22,11 +22,4 @@ package com.android.systemui.statusbar; */ public interface InflationTask { void abort(); - - /** - * Supersedes an existing task. i.e another task was superceeded by this. - * - * @param task the task that was previously running - */ - default void supersedeTask(InflationTask task) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7ad07c266cc3..7d3d4061014b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -20,7 +20,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.ColorStateList; -import android.content.res.Resources; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; @@ -46,6 +45,7 @@ import com.android.internal.widget.ViewClippingUtil; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -97,8 +97,6 @@ public class KeyguardIndicationController implements StateListener, private final LockPatternUtils mLockPatternUtils; private final DockManager mDockManager; - private final int mSlowThreshold; - private final int mFastThreshold; private final LockIcon mLockIcon; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); @@ -178,10 +176,6 @@ public class KeyguardIndicationController implements StateListener, mWakeLock = new SettableWakeLock(wakeLock, TAG); mLockPatternUtils = lockPatternUtils; - Resources res = context.getResources(); - mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold); - mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold); - mUserManager = context.getSystemService(UserManager.class); mBatteryInfo = iBatteryStats; @@ -484,12 +478,12 @@ public class KeyguardIndicationController implements StateListener, int chargingId; if (mPowerPluggedInWired) { switch (mChargingSpeed) { - case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST: + case BatteryStatus.CHARGING_FAST: chargingId = hasChargingTime ? R.string.keyguard_indication_charging_time_fast : R.string.keyguard_plugged_in_charging_fast; break; - case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY: + case BatteryStatus.CHARGING_SLOWLY: chargingId = hasChargingTime ? R.string.keyguard_indication_charging_time_slowly : R.string.keyguard_plugged_in_charging_slowly; @@ -620,7 +614,7 @@ public class KeyguardIndicationController implements StateListener, public static final int HIDE_DELAY_MS = 5000; @Override - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { + public void onRefreshBatteryInfo(BatteryStatus status) { boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING || status.status == BatteryManager.BATTERY_STATUS_FULL; boolean wasPluggedIn = mPowerPluggedIn; @@ -628,7 +622,7 @@ public class KeyguardIndicationController implements StateListener, mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull; mPowerCharged = status.isCharged(); mChargingWattage = status.maxChargingWattage; - mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); + mChargingSpeed = status.getChargingSpeed(mContext); mBatteryLevel = status.level; try { mChargingTimeRemaining = mPowerPluggedIn diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index d38f1b99cb9a..12298817d5a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -282,7 +282,8 @@ public class NotificationLockscreenUserManagerImpl implements filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter); + mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter, + null /* executor */, UserHandle.ALL); IntentFilter internalFilter = new IntentFilter(); internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoveInterceptor.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoveInterceptor.java index 930116ee4ae7..caa1e2db6ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoveInterceptor.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoveInterceptor.java @@ -16,8 +16,12 @@ package com.android.systemui.statusbar; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.service.notification.NotificationListenerService; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + /** * Interface for anything that may need to prevent notifications from being removed. This is * similar to a {@link NotificationLifetimeExtender} in the sense that it extends the life of @@ -30,11 +34,15 @@ public interface NotificationRemoveInterceptor { /** * Called when a notification has been removed. * - * @param key the entry key of the notification being removed. + * @param key the key of the notification being removed. Never null + * @param entry the entry of the notification being removed. * @param removeReason why the notification is being removed, e.g. * {@link NotificationListenerService#REASON_CANCEL} or 0 if unknown. * * @return true if the removal should be ignored, false otherwise. */ - boolean onNotificationRemoveRequested(String key, int removeReason); + boolean onNotificationRemoveRequested( + @NonNull String key, + @Nullable NotificationEntry entry, + int removeReason); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 8d4a9efbcd7a..37f9f88f6328 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -81,6 +82,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle private final BubbleController mBubbleController; private final DynamicPrivacyController mDynamicPrivacyController; private final KeyguardBypassController mBypassController; + private final ForegroundServiceSectionController mFgsSectionController; private final Context mContext; private NotificationPresenter mPresenter; @@ -101,7 +103,9 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle NotificationEntryManager notificationEntryManager, KeyguardBypassController bypassController, BubbleController bubbleController, - DynamicPrivacyController privacyController) { + DynamicPrivacyController privacyController, + ForegroundServiceSectionController fgsSectionController + ) { mContext = context; mHandler = mainHandler; mLockscreenUserManager = notificationLockscreenUserManager; @@ -110,6 +114,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle mVisualStabilityManager = visualStabilityManager; mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; mEntryManager = notificationEntryManager; + mFgsSectionController = fgsSectionController; Resources res = context.getResources(); mAlwaysExpandNonGroupedNotification = res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); @@ -140,7 +145,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle boolean hideMedia = Utils.useQsMediaPlayer(mContext); if (ent.isRowDismissed() || ent.isRowRemoved() || (ent.isMediaNotification() && hideMedia) - || mBubbleController.isBubbleNotificationSuppressedFromShade(ent)) { + || mBubbleController.isBubbleNotificationSuppressedFromShade(ent) + || mFgsSectionController.hasEntry(ent)) { // we don't want to update removed notifications because they could // temporarily become children if they were isolated before. continue; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt index 015c32348bb0..269a7a59f1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt @@ -43,10 +43,10 @@ class BypassHeadsUpNotifier @Inject constructor( private val headsUpManager: HeadsUpManagerPhone, private val notificationLockscreenUserManager: NotificationLockscreenUserManager, private val mediaManager: NotificationMediaManager, + private val entryManager: NotificationEntryManager, tunerService: TunerService ) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener { - private lateinit var entryManager: NotificationEntryManager private var currentMediaEntry: NotificationEntry? = null private var enabled = true @@ -70,8 +70,7 @@ class BypassHeadsUpNotifier @Inject constructor( }, Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING) } - fun setUp(entryManager: NotificationEntryManager) { - this.entryManager = entryManager + fun setUp() { mediaManager.addCallback(this) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt new file mode 100644 index 000000000000..b1d6b40fcc1e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import android.content.Context +import android.provider.DeviceConfig +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_ALLOW_FGS_DISMISSAL +import com.android.systemui.util.DeviceConfigProxy +import javax.inject.Inject +import javax.inject.Singleton + +private var sIsEnabled: Boolean? = null + +/** + * Feature controller for NOTIFICATIONS_ALLOW_FGS_DISMISSAL config. + */ +// TODO: this is really boilerplatey, make a base class that just wraps the device config +@Singleton +class ForegroundServiceDismissalFeatureController @Inject constructor( + val proxy: DeviceConfigProxy, + val context: Context +) { + fun isForegroundServiceDismissalEnabled(): Boolean { + return isEnabled(proxy) + } +} + +private fun isEnabled(proxy: DeviceConfigProxy): Boolean { + if (sIsEnabled == null) { + sIsEnabled = proxy.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, NOTIFICATIONS_ALLOW_FGS_DISMISSAL, false) + } + + return sIsEnabled!! +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java index 81833a4022a9..d0e238a66330 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java @@ -28,7 +28,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.policy.HeadsUpManager; import javax.inject.Inject; @@ -64,8 +63,8 @@ public class NotificationAlertingManager { notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override - public void onEntryInflated(NotificationEntry entry, int inflatedFlags) { - showAlertingView(entry, inflatedFlags); + public void onEntryInflated(NotificationEntry entry) { + showAlertingView(entry); } @Override @@ -90,12 +89,11 @@ public class NotificationAlertingManager { /** * Adds the entry to the respective alerting manager if the content view was inflated and * the entry should still alert. - * - * @param entry entry to add - * @param inflatedFlags flags representing content views that were inflated */ - private void showAlertingView(NotificationEntry entry, @InflationFlag int inflatedFlags) { - if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + private void showAlertingView(NotificationEntry entry) { + // TODO: Instead of this back and forth, we should listen to changes in heads up and + // cancel on-going heads up view inflation using the bind pipeline. + if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) { // Possible for shouldHeadsUp to change between the inflation starting and ending. // If it does and we no longer need to heads up, we should free the view. if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index f6b55838989c..25253a15b125 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -24,7 +24,6 @@ import androidx.annotation.NonNull; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; /** * Listener interface for changes sent by NotificationEntryManager. @@ -62,7 +61,7 @@ public interface NotificationEntryListener { /** * Called when a notification's views are inflated for the first time. */ - default void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) { + default void onEntryInflated(NotificationEntry entry) { } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 4a2283171694..916da6eca0c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_ERROR; +import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import android.annotation.NonNull; @@ -44,10 +45,9 @@ import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -96,6 +96,7 @@ import dagger.Lazy; */ @Singleton public class NotificationEntryManager implements + CommonNotifCollection, Dumpable, InflationCallback, VisualStabilityManager.Callback { @@ -126,25 +127,28 @@ public class NotificationEntryManager implements private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications = new ArrayMap<>(); + private final NotificationEntryManagerLogger mLogger; + // Lazily retrieved dependencies private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy; private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy; private final LeakDetector mLeakDetector; + private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); private final KeyguardEnvironment mKeyguardEnvironment; private final NotificationGroupManager mGroupManager; private final NotificationRankingManager mRankingManager; private final FeatureFlags mFeatureFlags; + private final ForegroundServiceDismissalFeatureController mFgsFeatureController; private NotificationPresenter mPresenter; private RankingMap mLatestRankingMap; - private NotifLog mNotifLog; @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders = new ArrayList<>(); private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>(); - private NotificationRemoveInterceptor mRemoveInterceptor; + private final List<NotificationRemoveInterceptor> mRemoveInterceptors = new ArrayList<>(); @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -157,6 +161,14 @@ public class NotificationEntryManager implements pw.println(entry.getSbn()); } } + pw.println(" Remove interceptors registered:"); + for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) { + pw.println(" " + interceptor.getClass().getSimpleName()); + } + pw.println(" Lifetime extenders registered:"); + for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { + pw.println(" " + extender.getClass().getSimpleName()); + } pw.println(" Lifetime-extended notifications:"); if (mRetainedNotifications.isEmpty()) { pw.println(" None"); @@ -171,15 +183,16 @@ public class NotificationEntryManager implements @Inject public NotificationEntryManager( - NotifLog notifLog, + NotificationEntryManagerLogger logger, NotificationGroupManager groupManager, NotificationRankingManager rankingManager, KeyguardEnvironment keyguardEnvironment, FeatureFlags featureFlags, Lazy<NotificationRowBinder> notificationRowBinderLazy, Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, - LeakDetector leakDetector) { - mNotifLog = notifLog; + LeakDetector leakDetector, + ForegroundServiceDismissalFeatureController fgsFeatureController) { + mLogger = logger; mGroupManager = groupManager; mRankingManager = rankingManager; mKeyguardEnvironment = keyguardEnvironment; @@ -187,6 +200,7 @@ public class NotificationEntryManager implements mNotificationRowBinderLazy = notificationRowBinderLazy; mRemoteInputManagerLazy = notificationRemoteInputManagerLazy; mLeakDetector = leakDetector; + mFgsFeatureController = fgsFeatureController; } /** Once called, the NEM will start processing notification events from system server. */ @@ -207,9 +221,14 @@ public class NotificationEntryManager implements mNotificationEntryListeners.remove(listener); } - /** Sets the {@link NotificationRemoveInterceptor}. */ - public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) { - mRemoveInterceptor = interceptor; + /** Add a {@link NotificationRemoveInterceptor}. */ + public void addNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) { + mRemoveInterceptors.add(interceptor); + } + + /** Remove a {@link NotificationRemoveInterceptor} */ + public void removeNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) { + mRemoveInterceptors.remove(interceptor); } public void setUpWithPresenter(NotificationPresenter presenter, @@ -271,13 +290,12 @@ public class NotificationEntryManager implements NotificationEntry entry = mPendingNotifications.get(key); entry.abortTask(); mPendingNotifications.remove(key); - mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry, "PendingNotification aborted" - + " reason=" + reason); + mLogger.logInflationAborted(key, "pending", reason); } NotificationEntry addedEntry = getActiveNotificationUnfiltered(key); if (addedEntry != null) { addedEntry.abortTask(); - mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getKey() + " " + reason); + mLogger.logInflationAborted(key, "active", reason); } } @@ -302,17 +320,16 @@ public class NotificationEntryManager implements } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { mPendingNotifications.remove(entry.getKey()); // If there was an async task started after the removal, we don't want to add it back to // the list, otherwise we might get leaks. if (!entry.isRowRemoved()) { boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null; + mLogger.logNotifInflated(entry.getKey(), isNew); if (isNew) { for (NotificationEntryListener listener : mNotificationEntryListeners) { - mNotifLog.log(NotifEvent.INFLATED, entry); - listener.onEntryInflated(entry, inflatedFlags); + listener.onEntryInflated(entry); } addActiveNotification(entry); updateNotifications("onAsyncInflationFinished"); @@ -321,7 +338,6 @@ public class NotificationEntryManager implements } } else { for (NotificationEntryListener listener : mNotificationEntryListeners) { - mNotifLog.log(NotifEvent.INFLATED, entry); listener.onEntryReinflated(entry); } } @@ -398,14 +414,16 @@ public class NotificationEntryManager implements boolean removedByUser, int reason) { - if (mRemoveInterceptor != null - && mRemoveInterceptor.onNotificationRemoveRequested(key, reason)) { - // Remove intercepted; log and skip - mNotifLog.log(NotifEvent.REMOVE_INTERCEPTED); - return; + final NotificationEntry entry = getActiveNotificationUnfiltered(key); + + for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) { + if (interceptor.onNotificationRemoveRequested(key, entry, reason)) { + // Remove intercepted; log and skip + mLogger.logRemovalIntercepted(key); + return; + } } - final NotificationEntry entry = getActiveNotificationUnfiltered(key); boolean lifetimeExtended = false; // Notification was canceled before it got inflated @@ -416,10 +434,7 @@ public class NotificationEntryManager implements if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) { extendLifetime(pendingEntry, extender); lifetimeExtended = true; - mNotifLog.log( - NotifEvent.LIFETIME_EXTENDED, - pendingEntry.getSbn(), - "pendingEntry extendedBy=" + extender.toString()); + mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending"); } } } @@ -439,10 +454,7 @@ public class NotificationEntryManager implements mLatestRankingMap = ranking; extendLifetime(entry, extender); lifetimeExtended = true; - mNotifLog.log( - NotifEvent.LIFETIME_EXTENDED, - entry.getSbn(), - "entry extendedBy=" + extender.toString()); + mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active"); break; } } @@ -465,11 +477,17 @@ public class NotificationEntryManager implements mLeakDetector.trackGarbage(entry); removedByUser |= entryDismissed; - mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(), - "removedByUser=" + removedByUser); + mLogger.logNotifRemoved(entry.getKey(), removedByUser); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryRemoved(entry, visibility, removedByUser); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + // NEM doesn't have a good knowledge of reasons so defaulting to unknown. + listener.onEntryRemoved(entry, REASON_UNKNOWN); + } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryCleanUp(entry); + } } } } @@ -528,10 +546,17 @@ public class NotificationEntryManager implements Ranking ranking = new Ranking(); rankingMap.getRanking(key, ranking); - NotificationEntry entry = new NotificationEntry(notification, ranking); + NotificationEntry entry = new NotificationEntry( + notification, + ranking, + mFgsFeatureController.isForegroundServiceDismissalEnabled()); mLeakDetector.trackInstance(entry); + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryInit(entry); + } + // Construct the expanded view. if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -541,10 +566,13 @@ public class NotificationEntryManager implements abortExistingInflation(key, "addNotification"); mPendingNotifications.put(key, entry); - mNotifLog.log(NotifEvent.NOTIF_ADDED, entry); + mLogger.logNotifAdded(entry.getKey()); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPendingEntryAdded(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryAdded(entry); + } } public void addNotification(StatusBarNotification notification, RankingMap ranking) { @@ -575,10 +603,13 @@ public class NotificationEntryManager implements entry.setSbn(notification); mGroupManager.onEntryUpdated(entry, oldSbn); - mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry); + mLogger.logNotifUpdated(entry.getKey()); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPreEntryUpdated(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryUpdated(entry); + } if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() @@ -653,6 +684,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onNotificationRankingUpdated(rankingMap); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingUpdate(rankingMap); + } } private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { @@ -764,7 +798,7 @@ public class NotificationEntryManager implements //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking /** * @param rankingMap the {@link RankingMap} to apply to the current notification list - * @param reason the reason for calling this method, for {@link NotifLog} + * @param reason the reason for calling this method, which will be logged */ public void updateRanking(RankingMap rankingMap, String reason) { updateRankingAndSort(rankingMap, reason); @@ -841,6 +875,11 @@ public class NotificationEntryManager implements return mReadOnlyNotifications.size() != 0; } + @Override + public void addCollectionListener(NotifCollectionListener listener) { + mNotifCollectionListeners.add(listener); + } + /* * End annexation * ----- diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt new file mode 100644 index 000000000000..4382ab50390a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +/** Logger for [NotificationEntryManager]. */ +class NotificationEntryManagerLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifAdded(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF ADDED $str1" + }) + } + + fun logNotifUpdated(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF UPDATED $str1" + }) + } + + fun logInflationAborted(key: String, status: String, reason: String) { + buffer.log(TAG, DEBUG, { + str1 = key + str2 = status + str3 = reason + }, { + "NOTIF INFLATION ABORTED $str1 notifStatus=$str2 reason=$str3" + }) + } + + fun logNotifInflated(key: String, isNew: Boolean) { + buffer.log(TAG, DEBUG, { + str1 = key + bool1 = isNew + }, { + "NOTIF INFLATED $str1 isNew=$bool1}" + }) + } + + fun logRemovalIntercepted(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "NOTIF REMOVE INTERCEPTED for $str1" + }) + } + + fun logLifetimeExtended(key: String, extenderName: String, status: String) { + buffer.log(TAG, INFO, { + str1 = key + str2 = extenderName + str3 = status + }, { + "NOTIF LIFETIME EXTENDED $str1 extender=$str2 status=$str3" + }) + } + + fun logNotifRemoved(key: String, removedByUser: Boolean) { + buffer.log(TAG, INFO, { + str1 = key + bool1 = removedByUser + }, { + "NOTIF REMOVED $str1 removedByUser=$bool1" + }) + } + + fun logFilterAndSort(reason: String) { + buffer.log(TAG, INFO, { + str1 = reason + }, { + "FILTER AND SORT reason=$str1" + }) + } +} + +private const val TAG = "NotificationEntryMgr"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index eaa9d78c08f4..3fa1954a7fcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.collection; +import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; + import java.util.Arrays; import java.util.List; @@ -120,6 +123,16 @@ public class ListDumper { .append(" "); } + if (!notifEntry.mDismissInterceptors.isEmpty()) { + String[] interceptorsNames = new String[notifEntry.mDismissInterceptors.size()]; + for (int i = 0; i < interceptorsNames.length; i++) { + interceptorsNames[i] = notifEntry.mDismissInterceptors.get(i).getName(); + } + rksb.append("dismissInterceptors=") + .append(Arrays.toString(interceptorsNames)) + .append(" "); + } + if (notifEntry.mExcludingFilter != null) { rksb.append("filter=") .append(notifEntry.mExcludingFilter) @@ -132,8 +145,20 @@ public class ListDumper { .append(" "); } + if (notifEntry.mCancellationReason != REASON_NOT_CANCELED) { + rksb.append("cancellationReason=") + .append(notifEntry.mCancellationReason) + .append(" "); + } + if (notifEntry.hasInflationError()) { - rksb.append("hasInflationError "); + rksb.append("(!)hasInflationError "); + } + + if (notifEntry.getDismissState() != NOT_DISMISSED) { + rksb.append("dismissState=") + .append(notifEntry.getDismissState()) + .append(" "); } String rkString = rksb.toString(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 8ac4d3008179..38d8d979a4da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -35,9 +35,14 @@ import static android.service.notification.NotificationListenerService.REASON_TI import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED; +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; + +import static java.util.Objects.requireNonNull; + import android.annotation.IntDef; import android.annotation.MainThread; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; import android.service.notification.NotificationListenerService.Ranking; @@ -45,6 +50,8 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import androidx.annotation.NonNull; + import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.DumpController; import com.android.systemui.Dumpable; @@ -56,6 +63,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; @@ -92,8 +100,8 @@ import javax.inject.Singleton; * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}). * * Interested parties can register listeners - * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are - * added, updated, or removed. + * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications + * events occur. */ @MainThread @Singleton @@ -109,6 +117,7 @@ public class NotifCollection implements Dumpable { @Nullable private CollectionReadyForBuildListener mBuildListener; private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); + private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>(); private boolean mAttached = false; private boolean mAmDispatchingToOtherCode; @@ -169,18 +178,84 @@ public class NotifCollection implements Dumpable { extender.setCallback(this::onEndLifetimeExtension); } + /** @see NotifPipeline#addNotificationDismissInterceptor(NotifDismissInterceptor) */ + void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) { + Assert.isMainThread(); + checkForReentrantCall(); + if (mDismissInterceptors.contains(interceptor)) { + throw new IllegalArgumentException("Interceptor " + interceptor + " already added."); + } + mDismissInterceptors.add(interceptor); + interceptor.setCallback(this::onEndDismissInterception); + } + /** * Dismiss a notification on behalf of the user. */ - void dismissNotification( - NotificationEntry entry, - @CancellationReason int reason, - @NonNull DismissedByUserStats stats) { + public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) { Assert.isMainThread(); - Objects.requireNonNull(stats); + requireNonNull(stats); checkForReentrantCall(); - removeNotification(entry.getKey(), null, reason, stats); + if (entry != mNotificationSet.get(entry.getKey())) { + throw new IllegalStateException("Invalid entry: " + entry.getKey()); + } + + if (entry.getDismissState() == DISMISSED) { + return; + } + + updateDismissInterceptors(entry); + if (isDismissIntercepted(entry)) { + mLogger.logNotifDismissedIntercepted(entry.getKey()); + return; + } + + // Optimistically mark the notification as dismissed -- we'll wait for the signal from + // system server before removing it from our notification set. + entry.setDismissState(DISMISSED); + mLogger.logNotifDismissed(entry.getKey()); + + List<NotificationEntry> canceledEntries = new ArrayList<>(); + + if (isCanceled(entry)) { + canceledEntries.add(entry); + } else { + // Ask system server to remove it for us + try { + mStatusBarService.onNotificationClear( + entry.getSbn().getPackageName(), + entry.getSbn().getTag(), + entry.getSbn().getId(), + entry.getSbn().getUser().getIdentifier(), + entry.getSbn().getKey(), + stats.dismissalSurface, + stats.dismissalSentiment, + stats.notificationVisibility); + } catch (RemoteException e) { + // system process is dead if we're here. + } + + // Also mark any children as dismissed as system server will auto-dismiss them as well + if (entry.getSbn().getNotification().isGroupSummary()) { + for (NotificationEntry otherEntry : mNotificationSet.values()) { + if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey()) + && otherEntry.getDismissState() != DISMISSED) { + otherEntry.setDismissState(PARENT_DISMISSED); + if (isCanceled(otherEntry)) { + canceledEntries.add(otherEntry); + } + } + } + } + } + + // Immediately remove any dismissed notifs that have already been canceled by system server + // (probably due to being lifetime-extended up until this point). + for (NotificationEntry canceledEntry : canceledEntries) { + tryRemoveNotification(canceledEntry); + } + rebuildList(); } private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { @@ -208,7 +283,15 @@ public class NotifCollection implements Dumpable { Assert.isMainThread(); mLogger.logNotifRemoved(sbn.getKey(), reason); - removeNotification(sbn.getKey(), rankingMap, reason, null); + + final NotificationEntry entry = mNotificationSet.get(sbn.getKey()); + if (entry == null) { + throw new IllegalStateException("No notification to remove with key " + sbn.getKey()); + } + entry.mCancellationReason = reason; + applyRanking(rankingMap); + tryRemoveNotification(entry); + rebuildList(); } private void onNotificationRankingUpdate(RankingMap rankingMap) { @@ -230,6 +313,8 @@ public class NotifCollection implements Dumpable { entry = new NotificationEntry(sbn, ranking); mNotificationSet.put(sbn.getKey(), entry); + dispatchOnEntryInit(entry); + if (rankingMap != null) { applyRanking(rankingMap); } @@ -240,9 +325,12 @@ public class NotifCollection implements Dumpable { // Update to an existing entry mLogger.logNotifUpdated(sbn.getKey()); - // Notification is updated so it is essentially re-added and thus alive again. Don't - // need to keep its lifetime extended. + // Notification is updated so it is essentially re-added and thus alive again, so we + // can reset its state. + cancelLocalDismissal(entry); cancelLifetimeExtension(entry); + cancelDismissInterception(entry); + entry.mCancellationReason = REASON_NOT_CANCELED; entry.setSbn(sbn); if (rankingMap != null) { @@ -253,70 +341,63 @@ public class NotifCollection implements Dumpable { } } - private void removeNotification( - String key, - @Nullable RankingMap rankingMap, - @CancellationReason int reason, - @Nullable DismissedByUserStats dismissedByUserStats) { + /** + * Tries to remove a notification from the notification set. This removal may be blocked by + * lifetime extenders. Does not trigger a rebuild of the list; caller must do that manually. + * + * @return True if the notification was removed, false otherwise. + */ + private boolean tryRemoveNotification(NotificationEntry entry) { + if (mNotificationSet.get(entry.getKey()) != entry) { + throw new IllegalStateException("No notification to remove with key " + entry.getKey()); + } - NotificationEntry entry = mNotificationSet.get(key); - if (entry == null) { - throw new IllegalStateException("No notification to remove with key " + key); + if (!isCanceled(entry)) { + throw new IllegalStateException("Cannot remove notification " + entry.getKey() + + ": has not been marked for removal"); } - entry.mLifetimeExtenders.clear(); - mAmDispatchingToOtherCode = true; - for (NotifLifetimeExtender extender : mLifetimeExtenders) { - if (extender.shouldExtendLifetime(entry, reason)) { - entry.mLifetimeExtenders.add(extender); - } + if (isDismissedByUser(entry)) { + // User-dismissed notifications cannot be lifetime-extended + cancelLifetimeExtension(entry); + } else { + updateLifetimeExtension(entry); } - mAmDispatchingToOtherCode = false; if (!isLifetimeExtended(entry)) { mNotificationSet.remove(entry.getKey()); - - if (dismissedByUserStats != null) { - try { - mStatusBarService.onNotificationClear( - entry.getSbn().getPackageName(), - entry.getSbn().getTag(), - entry.getSbn().getId(), - entry.getSbn().getUser().getIdentifier(), - entry.getSbn().getKey(), - dismissedByUserStats.dismissalSurface, - dismissedByUserStats.dismissalSentiment, - dismissedByUserStats.notificationVisibility); - } catch (RemoteException e) { - // system process is dead if we're here. - } - } - - if (rankingMap != null) { - applyRanking(rankingMap); - } - - dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */); + cancelDismissInterception(entry); + dispatchOnEntryRemoved(entry, entry.mCancellationReason); + dispatchOnEntryCleanUp(entry); + return true; + } else { + return false; } - - rebuildList(); } private void applyRanking(@NonNull RankingMap rankingMap) { for (NotificationEntry entry : mNotificationSet.values()) { - if (!isLifetimeExtended(entry)) { - Ranking ranking = requireRanking(rankingMap, entry.getKey()); - entry.setRanking(ranking); - - // TODO: (b/145659174) update the sbn's overrideGroupKey in - // NotificationEntry.setRanking instead of here once we fully migrate to the - // NewNotifPipeline - if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - final String newOverrideGroupKey = ranking.getOverrideGroupKey(); - if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), - newOverrideGroupKey)) { - entry.getSbn().setOverrideGroupKey(newOverrideGroupKey); + if (!isCanceled(entry)) { + + // TODO: (b/148791039) We should crash if we are ever handed a ranking with + // incomplete entries. Right now, there's a race condition in NotificationListener + // that means this might occur when SystemUI is starting up. + Ranking ranking = new Ranking(); + if (rankingMap.getRanking(entry.getKey(), ranking)) { + entry.setRanking(ranking); + + // TODO: (b/145659174) update the sbn's overrideGroupKey in + // NotificationEntry.setRanking instead of here once we fully migrate to the + // NewNotifPipeline + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + final String newOverrideGroupKey = ranking.getOverrideGroupKey(); + if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), + newOverrideGroupKey)) { + entry.getSbn().setOverrideGroupKey(newOverrideGroupKey); + } } + } else { + mLogger.logRankingMissing(entry.getKey(), rankingMap); } } } @@ -344,9 +425,9 @@ public class NotifCollection implements Dumpable { } if (!isLifetimeExtended(entry)) { - // TODO: This doesn't need to be undefined -- we can set either EXTENDER_EXPIRED or - // save the original reason - removeNotification(entry.getKey(), null, REASON_UNKNOWN, null); + if (tryRemoveNotification(entry)) { + rebuildList(); + } } } @@ -363,6 +444,78 @@ public class NotifCollection implements Dumpable { return entry.mLifetimeExtenders.size() > 0; } + private void updateLifetimeExtension(NotificationEntry entry) { + entry.mLifetimeExtenders.clear(); + mAmDispatchingToOtherCode = true; + for (NotifLifetimeExtender extender : mLifetimeExtenders) { + if (extender.shouldExtendLifetime(entry, entry.mCancellationReason)) { + entry.mLifetimeExtenders.add(extender); + } + } + mAmDispatchingToOtherCode = false; + } + + private void updateDismissInterceptors(@NonNull NotificationEntry entry) { + entry.mDismissInterceptors.clear(); + mAmDispatchingToOtherCode = true; + for (NotifDismissInterceptor interceptor : mDismissInterceptors) { + if (interceptor.shouldInterceptDismissal(entry)) { + entry.mDismissInterceptors.add(interceptor); + } + } + mAmDispatchingToOtherCode = false; + } + + private void cancelLocalDismissal(NotificationEntry entry) { + if (isDismissedByUser(entry)) { + entry.setDismissState(NOT_DISMISSED); + if (entry.getSbn().getNotification().isGroupSummary()) { + for (NotificationEntry otherEntry : mNotificationSet.values()) { + if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey()) + && otherEntry.getDismissState() == PARENT_DISMISSED) { + otherEntry.setDismissState(NOT_DISMISSED); + } + } + } + } + } + + private void onEndDismissInterception( + NotifDismissInterceptor interceptor, + NotificationEntry entry, + @NonNull DismissedByUserStats stats) { + Assert.isMainThread(); + if (!mAttached) { + return; + } + checkForReentrantCall(); + + if (!entry.mDismissInterceptors.remove(interceptor)) { + throw new IllegalStateException( + String.format( + "Cannot end dismiss interceptor for interceptor \"%s\" (%s)", + interceptor.getName(), + interceptor)); + } + + if (!isDismissIntercepted(entry)) { + dismissNotification(entry, stats); + } + } + + private void cancelDismissInterception(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifDismissInterceptor interceptor : entry.mDismissInterceptors) { + interceptor.cancelDismissInterception(entry); + } + mAmDispatchingToOtherCode = false; + entry.mDismissInterceptors.clear(); + } + + private boolean isDismissIntercepted(NotificationEntry entry) { + return entry.mDismissInterceptors.size() > 0; + } + private void checkForReentrantCall() { if (mAmDispatchingToOtherCode) { throw new IllegalStateException("Reentrant call detected"); @@ -378,6 +531,27 @@ public class NotifCollection implements Dumpable { return ranking; } + /** + * True if the notification has been canceled by system server. Usually, such notifications are + * immediately removed from the collection, but can sometimes stick around due to lifetime + * extenders. + */ + private static boolean isCanceled(NotificationEntry entry) { + return entry.mCancellationReason != REASON_NOT_CANCELED; + } + + private static boolean isDismissedByUser(NotificationEntry entry) { + return entry.getDismissState() != NOT_DISMISSED; + } + + private void dispatchOnEntryInit(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryInit(entry); + } + mAmDispatchingToOtherCode = false; + } + private void dispatchOnEntryAdded(NotificationEntry entry) { mAmDispatchingToOtherCode = true; for (NotifCollectionListener listener : mNotifCollectionListeners) { @@ -402,17 +576,36 @@ public class NotifCollection implements Dumpable { mAmDispatchingToOtherCode = false; } - private void dispatchOnEntryRemoved( - NotificationEntry entry, - @CancellationReason int reason, - boolean removedByUser) { + private void dispatchOnEntryRemoved(NotificationEntry entry, @CancellationReason int reason) { mAmDispatchingToOtherCode = true; for (NotifCollectionListener listener : mNotifCollectionListeners) { - listener.onEntryRemoved(entry, reason, removedByUser); + listener.onEntryRemoved(entry, reason); } mAmDispatchingToOtherCode = false; } + private void dispatchOnEntryCleanUp(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryCleanUp(entry); + } + mAmDispatchingToOtherCode = false; + } + @Override + public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) { + final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs()); + + pw.println("\t" + TAG + " unsorted/unfiltered notifications:"); + if (entries.size() == 0) { + pw.println("\t\t None"); + } + pw.println( + ListDumper.dumpList( + entries, + true, + "\t\t")); + } + private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() { @Override public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { @@ -467,20 +660,6 @@ public class NotifCollection implements Dumpable { @Retention(RetentionPolicy.SOURCE) public @interface CancellationReason {} + public static final int REASON_NOT_CANCELED = -1; public static final int REASON_UNKNOWN = 0; - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs()); - - pw.println("\t" + TAG + " unsorted/unfiltered notifications:"); - if (entries.size() == 0) { - pw.println("\t\t None"); - } - pw.println( - ListDumper.dumpList( - entries, - true, - "\t\t")); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index e7b772f1c7b2..9272e51b6aa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -98,13 +98,12 @@ public class NotifInflaterImpl implements NotifInflater { @Override public void run() { int dismissalSurface = NotificationStats.DISMISSAL_SHADE; - /** + /* * TODO: determine dismissal surface (ie: shade / headsup / aod) * see {@link NotificationLogger#logNotificationClear} */ mNotifCollection.dismissNotification( entry, - 0, new DismissedByUserStats( dismissalSurface, DISMISS_SENTIMENT_NEUTRAL, @@ -150,9 +149,7 @@ public class NotifInflaterImpl implements NotifInflater { } @Override - public void onAsyncInflationFinished( - NotificationEntry entry, - int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { if (mExternalInflationCallback != null) { mExternalInflationCallback.onInflationFinished(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java index 0377f57a7a9c..d4d2369ba822 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java @@ -23,7 +23,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import java.util.Collection; @@ -66,7 +68,7 @@ import javax.inject.Singleton; * 9. The list is handed off to the view layer to be rendered */ @Singleton -public class NotifPipeline { +public class NotifPipeline implements CommonNotifCollection { private final NotifCollection mNotifCollection; private final ShadeListBuilder mShadeListBuilder; @@ -89,22 +91,28 @@ public class NotifPipeline { return mNotifCollection.getActiveNotifs(); } - /** - * Registers a listener to be informed when notifications are added, removed or updated. - */ + @Override public void addCollectionListener(NotifCollectionListener listener) { mNotifCollection.addCollectionListener(listener); } /** * Registers a lifetime extender. Lifetime extenders can cause notifications that have been - * dismissed or retracted to be temporarily retained in the collection. + * dismissed or retracted by system server to be temporarily retained in the collection. */ public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { mNotifCollection.addNotificationLifetimeExtender(extender); } /** + * Registers a dismiss interceptor. Dismiss interceptors can cause notifications that have been + * dismissed by the user to be retained (won't send a dismissal to system server). + */ + public void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) { + mNotifCollection.addNotificationDismissInterceptor(interceptor); + } + + /** * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters * are called on each notification in the order that they were registered. If any filter * returns true, the notification is removed from the pipeline (and no other filters are diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 1f77ec229041..006d40ddbac5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -22,6 +22,7 @@ import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; import static android.app.Notification.FLAG_BUBBLE; +import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; @@ -29,9 +30,11 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING; -import android.annotation.NonNull; +import static java.util.Objects.requireNonNull; + import android.app.Notification; import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; @@ -50,6 +53,7 @@ import android.util.ArraySet; import android.view.View; import android.widget.ImageView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -59,10 +63,13 @@ import com.android.internal.util.ContrastColorUtil; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; @@ -99,15 +106,27 @@ public final class NotificationEntry extends ListEntry { /** List of lifetime extenders that are extending the lifetime of this notification. */ final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); + /** List of dismiss interceptors that are intercepting the dismissal of this notification. */ + final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>(); + /** If this notification was filtered out, then the filter that did the filtering. */ @Nullable NotifFilter mExcludingFilter; /** If this was a group child that was promoted to the top level, then who did the promoting. */ @Nullable NotifPromoter mNotifPromoter; - /** If this notification had an issue with inflating. Only used with the NewNotifPipeline **/ + /** + * If this notification was cancelled by system server, then the reason that was supplied. + * Uncancelled notifications always have REASON_NOT_CANCELED. Note that lifetime-extended + * notifications will have this set even though they are still in the active notification set. + */ + @CancellationReason int mCancellationReason = REASON_NOT_CANCELED; + + /** @see #hasInflationError() */ private boolean mHasInflationError; + /** @see #getDismissState() */ + @NonNull private DismissState mDismissState = DismissState.NOT_DISMISSED; /* * Old members @@ -135,6 +154,7 @@ public final class NotificationEntry extends ListEntry { private NotificationEntry parent; // our parent (if we're in a group) private ExpandableNotificationRow row; // the outer expanded view + private ExpandableNotificationRowController mRowController; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; @@ -164,18 +184,29 @@ public final class NotificationEntry extends ListEntry { private Runnable mOnSensitiveChangedListener; private boolean mAutoHeadsUp; private boolean mPulseSupressed; + private boolean mAllowFgsDismissal; private int mBucket = BUCKET_ALERTING; public NotificationEntry( @NonNull StatusBarNotification sbn, @NonNull Ranking ranking) { - super(Objects.requireNonNull(Objects.requireNonNull(sbn).getKey())); + this(sbn, ranking, false); + } + + public NotificationEntry( + @NonNull StatusBarNotification sbn, + @NonNull Ranking ranking, + boolean allowFgsDismissal + ) { + super(requireNonNull(Objects.requireNonNull(sbn).getKey())); - Objects.requireNonNull(ranking); + requireNonNull(ranking); mKey = sbn.getKey(); setSbn(sbn); setRanking(ranking); + + mAllowFgsDismissal = allowFgsDismissal; } @Override @@ -201,8 +232,8 @@ public final class NotificationEntry extends ListEntry { * TODO: Make this package-private */ public void setSbn(@NonNull StatusBarNotification sbn) { - Objects.requireNonNull(sbn); - Objects.requireNonNull(sbn.getKey()); + requireNonNull(sbn); + requireNonNull(sbn.getKey()); if (!sbn.getKey().equals(mKey)) { throw new IllegalArgumentException("New key " + sbn.getKey() @@ -227,8 +258,8 @@ public final class NotificationEntry extends ListEntry { * TODO: Make this package-private */ public void setRanking(@NonNull Ranking ranking) { - Objects.requireNonNull(ranking); - Objects.requireNonNull(ranking.getKey()); + requireNonNull(ranking); + requireNonNull(ranking.getKey()); if (!ranking.getKey().equals(mKey)) { throw new IllegalArgumentException("New key " + ranking.getKey() @@ -239,6 +270,39 @@ public final class NotificationEntry extends ListEntry { } /* + * Bookkeeping getters and setters + */ + + /** + * Whether this notification had an error when attempting to inflate. This is only used in + * the NewNotifPipeline + */ + public boolean hasInflationError() { + return mHasInflationError; + } + + /** + * Set whether the notification has an error while inflating. + * + * TODO: Move this into an inflation error manager class. + */ + public void setHasInflationError(boolean hasError) { + mHasInflationError = hasError; + } + + /** + * Set if the user has dismissed this notif but we haven't yet heard back from system server to + * confirm the dismissal. + */ + @NonNull public DismissState getDismissState() { + return mDismissState; + } + + void setDismissState(@NonNull DismissState dismissState) { + mDismissState = requireNonNull(dismissState); + } + + /* * Convenience getters for SBN and Ranking members */ @@ -275,7 +339,6 @@ public final class NotificationEntry extends ListEntry { return mRanking.canBubble(); } - public @NonNull List<Notification.Action> getSmartActions() { return mRanking.getSmartActions(); } @@ -363,6 +426,14 @@ public final class NotificationEntry extends ListEntry { this.row = row; } + public ExpandableNotificationRowController getRowController() { + return mRowController; + } + + public void setRowController(ExpandableNotificationRowController controller) { + mRowController = controller; + } + @Nullable public List<NotificationEntry> getChildren() { if (row == null) { @@ -543,12 +614,8 @@ public final class NotificationEntry extends ListEntry { public void setInflationTask(InflationTask abortableTask) { // abort any existing inflation - InflationTask existing = mRunningTask; abortTask(); mRunningTask = abortableTask; - if (existing != null && mRunningTask != null) { - mRunningTask.supersedeTask(existing); - } } public void onInflationTaskFinished() { @@ -578,18 +645,6 @@ public final class NotificationEntry extends ListEntry { remoteInputTextWhenReset = null; } - void setHasInflationError(boolean hasError) { - mHasInflationError = hasError; - } - - /** - * Whether this notification had an error when attempting to inflate. This is only used in - * the NewNotifPipeline - */ - public boolean hasInflationError() { - return mHasInflationError; - } - public void setHasSentReply() { hasSentReply = true; } @@ -799,8 +854,11 @@ public final class NotificationEntry extends ListEntry { * notification can be dismissed in case notifications are sensitive on the lockscreen. * @see #canViewBeDismissed() */ + // TOOD: This logic doesn't belong on NotificationEntry. It should be moved to the + // ForegroundsServiceDismissalFeatureController or some other controller that can be added + // as a dependency to any class that needs to answer this question. public boolean isClearable() { - if (!mSbn.isClearable()) { + if (!isDismissable()) { return false; } @@ -808,7 +866,7 @@ public final class NotificationEntry extends ListEntry { if (children != null && children.size() > 0) { for (int i = 0; i < children.size(); i++) { NotificationEntry child = children.get(i); - if (!child.isClearable()) { + if (!child.isDismissable()) { return false; } } @@ -816,6 +874,31 @@ public final class NotificationEntry extends ListEntry { return true; } + /** + * Notifications might have any combination of flags: + * - FLAG_ONGOING_EVENT + * - FLAG_NO_CLEAR + * - FLAG_FOREGROUND_SERVICE + * + * We want to allow dismissal of notifications that represent foreground services, which may + * have all 3 flags set. If we only find NO_CLEAR though, we don't want to allow dismissal + */ + private boolean isDismissable() { + boolean ongoing = ((mSbn.getNotification().flags & Notification.FLAG_ONGOING_EVENT) != 0); + boolean noclear = ((mSbn.getNotification().flags & Notification.FLAG_NO_CLEAR) != 0); + boolean fgs = ((mSbn.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0); + + if (mAllowFgsDismissal) { + if (noclear && !ongoing && !fgs) { + return false; + } + return true; + } else { + return mSbn.isClearable(); + } + + } + public boolean canViewBeDismissed() { if (row == null) return true; return row.canViewBeDismissed(); @@ -974,6 +1057,16 @@ public final class NotificationEntry extends ListEntry { } } + /** @see #getDismissState() */ + public enum DismissState { + /** User has not dismissed this notif or its parent */ + NOT_DISMISSED, + /** User has dismissed this notif specifically */ + DISMISSED, + /** User has dismissed this notif's parent (which implicitly dismisses this one as well) */ + PARENT_DISMISSED, + } + private static final long LAUNCH_COOLDOWN = 2000; private static final long REMOTE_INPUT_COOLDOWN = 500; private static final long INITIALIZATION_DELAY = 400; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 1eeeab3e93cb..2981252f148c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -22,11 +22,10 @@ import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider -import com.android.systemui.statusbar.notification.logging.NotifEvent -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE @@ -53,7 +52,7 @@ open class NotificationRankingManager @Inject constructor( private val groupManager: NotificationGroupManager, private val headsUpManager: HeadsUpManager, private val notifFilter: NotificationFilter, - private val notifLog: NotifLog, + private val logger: NotificationEntryManagerLogger, sectionsFeatureManager: NotificationSectionsFeatureManager, private val peopleNotificationIdentifier: PeopleNotificationIdentifier, private val highPriorityProvider: HighPriorityProvider @@ -134,7 +133,7 @@ open class NotificationRankingManager @Inject constructor( entries: Sequence<NotificationEntry>, reason: String ): Sequence<NotificationEntry> { - notifLog.log(NotifEvent.FILTER_AND_SORT, reason) + logger.logFilterAndSort(reason) return entries.filter { !notifFilter.shouldFilterOut(it) } .sortedWith(rankingComparator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java new file mode 100644 index 000000000000..116c70c4f1cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static android.service.notification.NotificationStats.DISMISSAL_OTHER; +import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; + +import java.util.HashSet; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Coordinates hiding, intercepting (the dismissal), and deletion of bubbled notifications. + * + * The typical "start state" for a bubbled notification is when a bubble-able notification is + * posted. It is visible as a bubble AND as a notification in the shade. From here, we can get + * into a few hidden-from-shade states described below: + * + * Start State -> Hidden from shade + * User expands the bubble so we hide its notification from the shade. + * OR + * User dismisses a group summary with a bubbled child. All bubbled children are now hidden from + * the shade. And the group summary's dismissal is intercepted + hidden from the shade (see below). + * + * Start State -> Dismissal intercepted + hidden from shade + * User dismisses the notification from the shade. We now hide the notification from the shade + * and intercept its dismissal (the removal signal is never sent to system server). We + * keep the notification alive in system server so that {@link BubbleController} can still + * respond to app-cancellations (ie: remove the bubble if the app cancels the notification). + * + */ +@Singleton +public class BubbleCoordinator implements Coordinator { + private static final String TAG = "BubbleCoordinator"; + + private final BubbleController mBubbleController; + private final NotifCollection mNotifCollection; + private final Set<String> mInterceptedDismissalEntries = new HashSet<>(); + private NotifPipeline mNotifPipeline; + private NotifDismissInterceptor.OnEndDismissInterception mOnEndDismissInterception; + + @Inject + public BubbleCoordinator( + BubbleController bubbleController, + NotifCollection notifCollection) { + mBubbleController = bubbleController; + mNotifCollection = notifCollection; + } + + @Override + public void attach(NotifPipeline pipeline) { + mNotifPipeline = pipeline; + mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor); + mNotifPipeline.addPreRenderFilter(mNotifFilter); + mBubbleController.addNotifCallback(mNotifCallback); + } + + private final NotifFilter mNotifFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return mBubbleController.isBubbleNotificationSuppressedFromShade(entry); + } + }; + + private final NotifDismissInterceptor mDismissInterceptor = new NotifDismissInterceptor() { + @Override + public String getName() { + return TAG; + } + + @Override + public void setCallback(OnEndDismissInterception callback) { + mOnEndDismissInterception = callback; + } + + @Override + public boolean shouldInterceptDismissal(NotificationEntry entry) { + // TODO: b/149041810 add support for intercepting app-cancelled bubble notifications + // for experimental bubbles + if (mBubbleController.handleDismissalInterception(entry)) { + mInterceptedDismissalEntries.add(entry.getKey()); + return true; + } else { + mInterceptedDismissalEntries.remove(entry.getKey()); + return false; + } + } + + @Override + public void cancelDismissInterception(NotificationEntry entry) { + mInterceptedDismissalEntries.remove(entry.getKey()); + } + }; + + private final BubbleController.NotifCallback mNotifCallback = + new BubbleController.NotifCallback() { + @Override + public void removeNotification(NotificationEntry entry, int reason) { + if (isInterceptingDismissal(entry)) { + mInterceptedDismissalEntries.remove(entry.getKey()); + mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry, + createDismissedByUserStats(entry)); + } else if (mNotifPipeline.getActiveNotifs().contains(entry)) { + // Bubbles are hiding the notifications from the shade, but the bubble was + // deleted; therefore, the notification should be cancelled as if it were a user + // dismissal (this won't re-enter handleInterceptDimissal because Bubbles + // will have already marked it as no longer a bubble) + mNotifCollection.dismissNotification(entry, createDismissedByUserStats(entry)); + } + } + + @Override + public void invalidateNotifications(String reason) { + mNotifFilter.invalidateList(); + } + + @Override + public void maybeCancelSummary(NotificationEntry entry) { + // no-op + } + }; + + private boolean isInterceptingDismissal(NotificationEntry entry) { + return mInterceptedDismissalEntries.contains(entry.getKey()); + } + + private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) { + return new DismissedByUserStats( + DISMISSAL_OTHER, + DISMISS_SENTIMENT_UNKNOWN, + NotificationVisibility.obtain(entry.getKey(), + entry.getRanking().getRank(), + mNotifPipeline.getActiveNotifs().size(), + true, // was visible as a bubble + NotificationLogger.getNotificationLocation(entry)) + ); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java new file mode 100644 index 000000000000..0059e7baa3c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; + +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +/** + * Filters out notifications that have been dismissed locally (by the user) but that system server + * hasn't yet confirmed the removal of. + */ +public class HideLocallyDismissedNotifsCoordinator implements Coordinator { + @Override + public void attach(NotifPipeline pipeline) { + pipeline.addPreGroupFilter(mFilter); + } + + private final NotifFilter mFilter = new NotifFilter("HideLocallyDismissedNotifsFilter") { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return entry.getDismissState() != NOT_DISMISSED; + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 8d0dd5b111ba..7a9547c573bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -53,13 +53,16 @@ public class NotifCoordinators implements Dumpable { RankingCoordinator rankingCoordinator, ForegroundCoordinator foregroundCoordinator, DeviceProvisionedCoordinator deviceProvisionedCoordinator, + BubbleCoordinator bubbleCoordinator, PreparationCoordinator preparationCoordinator) { dumpController.registerDumpable(TAG, this); + mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); mCoordinators.add(keyguardCoordinator); mCoordinators.add(rankingCoordinator); mCoordinators.add(foregroundCoordinator); mCoordinators.add(deviceProvisionedCoordinator); + mCoordinators.add(bubbleCoordinator); if (featureFlags.isNewNotifPipelineRenderingEnabled()) { mCoordinators.add(preparationCoordinator); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 20c9cbc8790d..1e5946a85cfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -22,8 +22,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; import java.util.ArrayList; import java.util.List; @@ -42,13 +40,15 @@ import javax.inject.Singleton; public class PreparationCoordinator implements Coordinator { private static final String TAG = "PreparationCoordinator"; - private final NotifLog mNotifLog; + private final PreparationCoordinatorLogger mLogger; private final NotifInflater mNotifInflater; private final List<NotificationEntry> mPendingNotifications = new ArrayList<>(); @Inject - public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) { - mNotifLog = notifLog; + public PreparationCoordinator( + PreparationCoordinatorLogger logger, + NotifInflaterImpl notifInflater) { + mLogger = logger; mNotifInflater = notifInflater; mNotifInflater.setInflationCallback(mInflationCallback); } @@ -72,7 +72,7 @@ public class PreparationCoordinator implements Coordinator { } @Override - public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) { + public void onEntryRemoved(NotificationEntry entry, int reason) { abortInflation(entry, "entryRemoved reason=" + reason); } }; @@ -106,7 +106,7 @@ public class PreparationCoordinator implements Coordinator { new NotifInflater.InflationCallback() { @Override public void onInflationFinished(NotificationEntry entry) { - mNotifLog.log(NotifEvent.INFLATED, entry); + mLogger.logNotifInflated(entry.getKey()); mPendingNotifications.remove(entry); mNotifInflatingFilter.invalidateList(); } @@ -123,7 +123,7 @@ public class PreparationCoordinator implements Coordinator { } private void abortInflation(NotificationEntry entry, String reason) { - mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason); + mLogger.logInflationAborted(entry.getKey(), reason); entry.abortTask(); mPendingNotifications.remove(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt new file mode 100644 index 000000000000..75e7bc9b79a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class PreparationCoordinatorLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifInflated(key: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + }, { + "NOTIF INFLATED $str1" + }) + } + + fun logInflationAborted(key: String, reason: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = key + str2 = reason + }, { + "NOTIF INFLATION ABORTED $str1 reason=$str2" + }) + } +} + +private const val TAG = "PreparationCoordinator"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 2a7683a8c7c2..e8a62e48e75e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; -import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import android.annotation.Nullable; @@ -40,16 +39,19 @@ import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.Objects; @@ -64,36 +66,32 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private static final String TAG = "NotificationViewManager"; - private final NotificationGroupManager mGroupManager; - private final NotificationGutsManager mGutsManager; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; + private final Context mContext; - private final NotificationRowContentBinder mRowContentBinder; + private final NotifBindPipeline mNotifBindPipeline; + private final RowContentBindStage mRowContentBindStage; private final NotificationMessagingUtil mMessagingUtil; - private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = - this::logNotificationExpansion; private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; - private final boolean mAllowLongPress; - private final KeyguardBypassController mKeyguardBypassController; - private final StatusBarStateController mStatusBarStateController; private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; - private HeadsUpManager mHeadsUpManager; private NotificationRowContentBinder.InflationCallback mInflationCallback; - private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; private final Provider<RowInflaterTask> mRowInflaterTaskProvider; - private final NotificationLogger mNotificationLogger; + private final ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; @Inject public NotificationRowBinderImpl( Context context, + NotificationMessagingUtil notificationMessagingUtil, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, - NotificationRowContentBinder rowContentBinder, + NotifBindPipeline notifBindPipeline, + RowContentBindStage rowContentBindStage, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, KeyguardBypassController keyguardBypassController, StatusBarStateController statusBarStateController, @@ -101,20 +99,16 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { NotificationGutsManager notificationGutsManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, Provider<RowInflaterTask> rowInflaterTaskProvider, - NotificationLogger logger) { + ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) { mContext = context; - mRowContentBinder = rowContentBinder; - mMessagingUtil = new NotificationMessagingUtil(context); + mNotifBindPipeline = notifBindPipeline; + mRowContentBindStage = rowContentBindStage; + mMessagingUtil = notificationMessagingUtil; mNotificationRemoteInputManager = notificationRemoteInputManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; - mAllowLongPress = allowLongPress; - mKeyguardBypassController = keyguardBypassController; - mStatusBarStateController = statusBarStateController; - mGroupManager = notificationGroupManager; - mGutsManager = notificationGutsManager; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mRowInflaterTaskProvider = rowInflaterTaskProvider; - mNotificationLogger = logger; + mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder; } /** @@ -122,13 +116,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { */ public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, - HeadsUpManager headsUpManager, BindRowCallback bindRowCallback) { mPresenter = presenter; mListContainer = listContainer; - mHeadsUpManager = headsUpManager; mBindRowCallback = bindRowCallback; - mOnAppOpsClickListener = mGutsManager::openGuts; } public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { @@ -143,9 +134,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { * Inflates the views for the given entry (possibly asynchronously). */ @Override - public void inflateViews( - NotificationEntry entry, - Runnable onDismissRunnable) + public void inflateViews(NotificationEntry entry, Runnable onDismissRunnable) throws InflationException { ViewGroup parent = mListContainer.getViewParentForNotification(entry); PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, @@ -156,64 +145,39 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { entry.updateIcons(mContext, sbn); entry.reset(); updateNotification(entry, pmUser, sbn, entry.getRow()); - entry.getRow().setOnDismissRunnable(onDismissRunnable); + entry.getRowController().setOnDismissRunnable(onDismissRunnable); } else { entry.createIcons(mContext, sbn); mRowInflaterTaskProvider.get().inflate(mContext, parent, entry, row -> { - bindRow(entry, pmUser, sbn, row, onDismissRunnable); + // Setup the controller for the view. + ExpandableNotificationRowComponent component = + mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(row) + .notificationEntry(entry) + .onDismissRunnable(onDismissRunnable) + .inflationCallback(mInflationCallback) + .rowContentBindStage(mRowContentBindStage) + .onExpandClickListener(mPresenter) + .build(); + ExpandableNotificationRowController rowController = + component.getExpandableNotificationRowController(); + rowController.init(); + entry.setRowController(rowController); + bindRow(entry, pmUser, sbn, row); updateNotification(entry, pmUser, sbn, row); }); } } + //TODO: This method associates a row with an entry, but eventually needs to not do that private void bindRow(NotificationEntry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row, - Runnable onDismissRunnable) { - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (PackageManager.NameNotFoundException e) { - // Do nothing - } - - row.initialize( - appname, - sbn.getKey(), - mExpansionLogger, - mKeyguardBypassController, - mGroupManager, - mHeadsUpManager, - mRowContentBinder, - mPresenter); - - // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely - row.setStatusBarStateController(mStatusBarStateController); - row.setInflationCallback(mInflationCallback); - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } + StatusBarNotification sbn, ExpandableNotificationRow row) { mListContainer.bindRow(row); mNotificationRemoteInputManager.bindRow(row); - - row.setOnDismissRunnable(onDismissRunnable); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - + entry.setRow(row); + row.setEntry(entry); + mNotifBindPipeline.manageRow(entry, row); mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } @@ -247,13 +211,11 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } } - //TODO: This method associates a row with an entry, but eventually needs to not do that private void updateNotification( NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setIsLowPriority(entry.isAmbient()); // Extract target SDK version. try { @@ -268,31 +230,36 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { // TODO: should updates to the entry be happening somewhere else? entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.setRow(row); row.setOnActivatedListener(mPresenter); - boolean useIncreasedCollapsedHeight = + final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, entry.getImportance()); - boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight + final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && !mPresenter.isPresenterFullyCollapsed(); - row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.setEntry(entry); + final boolean isLowPriority = entry.isAmbient(); + + RowContentBindParams params = mRowContentBindStage.getStageParams(entry); + params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + params.setUseLowPriority(entry.isAmbient()); if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { - row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); } + //TODO: Replace this API with RowContentBindParams directly row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); - row.inflateViews(); + params.rebindAllContentViews(); + mRowContentBindStage.requestRebind(entry, en -> { + row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp); + row.setIsLowPriority(isLowPriority); + mInflationCallback.onAsyncInflationFinished(en); + }); // bind the click event to the content area Objects.requireNonNull(mNotificationClicker).register(row, sbn); } - private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mNotificationLogger.onExpansionChanged(key, userAction, expanded); - } - /** Callback for when a row is bound to an entry. */ public interface BindRowCallback { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java new file mode 100644 index 000000000000..171816fd28da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.notifcollection; + +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * A notification collection that manages the list of {@link NotificationEntry}s that will be + * rendered. + * + * TODO: (b/145659174) Once we fully switch off {@link NotificationEntryManager} to + * {@link NotifPipeline}, we probably won't need this, but having it for now makes it easy to + * switch between the two. + */ +public interface CommonNotifCollection { + /** + * Registers a listener to be informed when notifications are created, added, updated, removed, + * or deleted. + */ + void addCollectionListener(NotifCollectionListener listener); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 6adcabd86fe6..b2c53dae16cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -18,15 +18,25 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; import android.service.notification.NotificationListenerService; -import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** - * Listener interface for {@link NotifCollection}. + * Listener interface for {@link NotificationEntry} events. */ public interface NotifCollectionListener { /** + * Called whenever a new {@link NotificationEntry} is initialized. This should be used for + * initializing any decorated state tied to the notification. + * + * Do not reference other registered {@link NotifCollectionListener} implementations here as + * there is no guarantee of order and they may not have had a chance to initialize yet. Instead, + * use {@link #onEntryAdded} which is called after all initialization. + */ + default void onEntryInit(NotificationEntry entry) { + } + + /** * Called whenever a notification with a new key is posted. */ default void onEntryAdded(NotificationEntry entry) { @@ -44,10 +54,19 @@ public interface NotifCollectionListener { * immediately after a user dismisses a notification: we wait until we receive confirmation from * system server before considering the notification removed. */ - default void onEntryRemoved( - NotificationEntry entry, - @CancellationReason int reason, - boolean removedByUser) { + default void onEntryRemoved(NotificationEntry entry, @CancellationReason int reason) { + } + + /** + * Called whenever a {@link NotificationEntry} is considered deleted. This should be used for + * cleaning up any state tied to the notification. + * + * This is the deletion parallel of {@link #onEntryInit} and similarly means that you cannot + * expect other {@link NotifCollectionListener} implementations to still have valid state for + * the entry during this call. Instead, use {@link #onEntryRemoved} which will be called before + * deletion. + */ + default void onEntryCleanUp(NotificationEntry entry) { } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index bd1bd860f80c..dc7a50d621a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -16,8 +16,11 @@ package com.android.systemui.statusbar.notification.collection.notifcollection +import android.service.notification.NotificationListenerService.RankingMap import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationLog import javax.inject.Inject @@ -25,7 +28,7 @@ class NotifCollectionLogger @Inject constructor( @NotificationLog private val buffer: LogBuffer ) { fun logNotifPosted(key: String) { - buffer.log(TAG, LogLevel.INFO, { + buffer.log(TAG, INFO, { str1 = key }, { "POSTED $str1" @@ -33,7 +36,7 @@ class NotifCollectionLogger @Inject constructor( } fun logNotifGroupPosted(groupKey: String, batchSize: Int) { - buffer.log(TAG, LogLevel.INFO, { + buffer.log(TAG, INFO, { str1 = groupKey int1 = batchSize }, { @@ -42,7 +45,7 @@ class NotifCollectionLogger @Inject constructor( } fun logNotifUpdated(key: String) { - buffer.log(TAG, LogLevel.INFO, { + buffer.log(TAG, INFO, { str1 = key }, { "UPDATED $str1" @@ -50,13 +53,37 @@ class NotifCollectionLogger @Inject constructor( } fun logNotifRemoved(key: String, reason: Int) { - buffer.log(TAG, LogLevel.INFO, { + buffer.log(TAG, INFO, { str1 = key int1 = reason }, { "REMOVED $str1 reason=$int1" }) } + + fun logNotifDismissed(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "DISMISSED $str1" + }) + } + + fun logNotifDismissedIntercepted(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "DISMISS INTERCEPTED $str1" + }) + } + + fun logRankingMissing(key: String, rankingMap: RankingMap) { + buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" }) + buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" }) + for (entry in rankingMap.orderedKeys) { + buffer.log(TAG, DEBUG, { str1 = entry }, { " $str1" }) + } + } } private const val TAG = "NotifCollection"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java new file mode 100644 index 000000000000..3354ad1daf20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifDismissInterceptor.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.notifcollection; + +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * A way for coordinators to temporarily intercept a user-dismissed notification before a message + * is sent to system server to officially remove this notification. + * See {@link NotifCollection#addNotificationDismissInterceptor(NotifDismissInterceptor)}. + */ +public interface NotifDismissInterceptor { + /** Name to associate with this interceptor (for the purposes of debugging) */ + String getName(); + + /** + * Called on the interceptor immediately after it has been registered. The interceptor should + * hang on to this callback and execute it whenever it no longer needs to intercept the + * dismissal of the notification. + */ + void setCallback(OnEndDismissInterception callback); + + /** + * Called by the NotifCollection whenever a notification has been dismissed (by the user). + * If the interceptor returns true, it is considered to be intercepting the notification. + * Intercepted notifications will not be sent to system server for removal until it is no + * longer being intercepted. However, the notification can still be cancelled by the app. + * This method is called on all interceptors even if earlier ones return true. + */ + boolean shouldInterceptDismissal(NotificationEntry entry); + + + /** + * Called by the NotifCollection to inform a DismissInterceptor that its interception of a notif + * is no longer valid (usually because the notif has been removed by means other than the + * user dismissing the notification from the shade, or the notification has been updated). The + * interceptor should clean up any references it has to the notif in question. + */ + void cancelDismissInterception(NotificationEntry entry); + + /** + * Callback for notifying the NotifCollection that it no longer is intercepting the dismissal. + * If the end of this dismiss interception triggers a dismiss (ie: no other + * NotifDismissInterceptors are intercepting the entry), NotifCollection will use stats + * in the message sent to system server for the notification's dismissal. + */ + interface OnEndDismissInterception { + void onEndDismissInterception( + NotifDismissInterceptor interceptor, + NotificationEntry entry, + DismissedByUserStats stats); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java new file mode 100644 index 000000000000..39f4dfacc2c5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.dagger; + +import android.content.Context; + +import com.android.systemui.R; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.init.NotificationsController; +import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; +import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; + +import javax.inject.Singleton; + +import dagger.Lazy; +import dagger.Module; +import dagger.Provides; + +/** Module for classes related to the notifications data pipeline */ +@Module +public class NotificationsModule { + /** Initializes the notification data pipeline (can be disabled via config). */ + @Singleton + @Provides + static NotificationsController provideNotificationsController( + Context context, + Lazy<NotificationsControllerImpl> realController, + Lazy<NotificationsControllerStub> stubController) { + if (context.getResources().getBoolean(R.bool.config_renderNotifications)) { + return realController.get(); + } else { + return stubController.get(); + } + } + + /** + * Provide the active notification collection managing the notifications to render. + */ + @Provides + @Singleton + public CommonNotifCollection provideCommonNotifCollection( + FeatureFlags featureFlags, + Lazy<NotifPipeline> pipeline, + NotificationEntryManager entryManager) { + return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt new file mode 100644 index 000000000000..9da8b8a3fd92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.StatusBar +import java.io.FileDescriptor +import java.io.PrintWriter + +/** + * The master controller for all notifications-related work + * + * Split into two implementations: [NotificationsControllerImpl] (most cases) and + * [NotificationsControllerStub] (for builds that disable notification rendering). + */ +interface NotificationsController { + fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) + + fun requestNotificationUpdate(reason: String) + fun resetUserExpandedStates() + fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) + fun getActiveNotificationsCount(): Int + fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) + fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>, dumpTruck: Boolean) +} 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 new file mode 100644 index 000000000000..3e0bcbb796bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.bubbles.BubbleController +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.FeatureFlags +import com.android.systemui.statusbar.NotificationListener +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.NotificationClicker +import com.android.systemui.statusbar.notification.NotificationEntryManager +import com.android.systemui.statusbar.notification.NotificationListController +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper +import com.android.systemui.statusbar.phone.NotificationGroupManager +import com.android.systemui.statusbar.phone.StatusBar +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.RemoteInputUriController +import dagger.Lazy +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.Optional +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Master controller for all notifications-related work + * + * At the moment exposes a number of event-handler-esque methods; these are for historical reasons. + * Once we migrate away from the need for such things, this class becomes primarily a place to do + * any initialization work that notifications require. + */ +@Singleton +class NotificationsControllerImpl @Inject constructor( + private val featureFlags: FeatureFlags, + private val notificationListener: NotificationListener, + private val entryManager: NotificationEntryManager, + private val newNotifPipeline: Lazy<NotifPipelineInitializer>, + private val notifBindPipelineInitializer: NotifBindPipelineInitializer, + private val deviceProvisionedController: DeviceProvisionedController, + private val notificationRowBinder: NotificationRowBinderImpl, + private val remoteInputUriController: RemoteInputUriController, + private val bubbleController: BubbleController, + private val groupManager: NotificationGroupManager, + private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, + private val headsUpManager: HeadsUpManager +) : NotificationsController { + + override fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) { + notificationListener.registerAsSystemService() + + val listController = + NotificationListController( + entryManager, + listContainer, + deviceProvisionedController) + listController.bind() + + notificationRowBinder.setNotificationClicker( + NotificationClicker( + Optional.of(statusBar), + bubbleController, + notificationActivityStarter)) + notificationRowBinder.setUpWithPresenter( + presenter, + listContainer, + bindRowCallback) + + if (featureFlags.isNewNotifPipelineEnabled) { + newNotifPipeline.get().initialize(notificationListener, notificationRowBinder) + } + + if (featureFlags.isNewNotifPipelineRenderingEnabled) { + // TODO + } else { + notifBindPipelineInitializer.initialize() + notificationRowBinder.setInflationCallback(entryManager) + + remoteInputUriController.attach(entryManager) + groupAlertTransferHelper.bind(entryManager, groupManager) + headsUpManager.addListener(groupManager) + headsUpManager.addListener(groupAlertTransferHelper) + groupManager.setHeadsUpManager(headsUpManager) + groupAlertTransferHelper.setHeadsUpManager(headsUpManager) + + entryManager.attach(notificationListener) + } + } + + override fun dump( + fd: FileDescriptor, + pw: PrintWriter, + args: Array<String>, + dumpTruck: Boolean + ) { + if (dumpTruck) { + entryManager.dump(pw, " ") + } + groupManager.dump(fd, pw, args) + } + + // TODO: Convert all functions below this line into listeners instead of public methods + + override fun requestNotificationUpdate(reason: String) { + entryManager.updateNotifications(reason) + } + + override fun resetUserExpandedStates() { + for (entry in entryManager.visibleNotifications) { + entry.resetUserExpansion() + } + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) { + if (snoozeOption.snoozeCriterion != null) { + notificationListener.snoozeNotification(sbn.key, snoozeOption.snoozeCriterion.id) + } else { + notificationListener.snoozeNotification( + sbn.key, + snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong()) + } + } + + override fun getActiveNotificationsCount(): Int { + return entryManager.activeNotificationsCount + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) { + notificationListener.snoozeNotification( + sbn.key, + hoursToSnooze * 60 * 60 * 1000.toLong()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt new file mode 100644 index 000000000000..ded855dd84f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.init + +import android.service.notification.StatusBarNotification +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption +import com.android.systemui.statusbar.NotificationListener +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.StatusBar +import java.io.FileDescriptor +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Implementation of [NotificationsController] that's used when notifications rendering is disabled. + */ +class NotificationsControllerStub @Inject constructor( + private val notificationListener: NotificationListener +) : NotificationsController { + + override fun initialize( + statusBar: StatusBar, + presenter: NotificationPresenter, + listContainer: NotificationListContainer, + notificationActivityStarter: NotificationActivityStarter, + bindRowCallback: NotificationRowBinderImpl.BindRowCallback + ) { + // Always connect the listener even if notification-handling is disabled. Being a listener + // grants special permissions and it's not clear if other things will break if we lose those + notificationListener.registerAsSystemService() + } + + override fun requestNotificationUpdate(reason: String) { + } + + override fun resetUserExpandedStates() { + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) { + } + + override fun setNotificationSnoozed(sbn: StatusBarNotification, hoursToSnooze: Int) { + } + + override fun getActiveNotificationsCount(): Int { + return 0 + } + + override fun dump( + fd: FileDescriptor, + pw: PrintWriter, + args: Array<String>, + dumpTruck: Boolean + ) { + pw.println() + pw.println("Notification handling disabled") + pw.println() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java deleted file mode 100644 index 9adceb78c249..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java +++ /dev/null @@ -1,212 +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.logging; - -import android.annotation.IntDef; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.log.RichEvent; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; -import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * An event related to notifications. {@link NotifLog} stores and prints these events for debugging - * and triaging purposes. We do not store a copy of the status bar notification nor ranking - * here to mitigate memory usage. - */ -public class NotifEvent extends RichEvent { - /** - * Initializes a rich event that includes an event type that matches with an index in the array - * getEventLabels(). - */ - public NotifEvent init(@EventType int type, StatusBarNotification sbn, - NotificationListenerService.Ranking ranking, String reason) { - StringBuilder extraInfo = new StringBuilder(reason); - if (sbn != null) { - extraInfo.append(" " + sbn.getKey()); - } - - if (ranking != null) { - extraInfo.append(" Ranking="); - extraInfo.append(ranking.getRank()); - } - super.init(INFO, type, extraInfo.toString()); - return this; - } - - /** - * Event labels for ListBuilderEvents - * Index corresponds to an # in {@link EventType} - */ - @Override - public String[] getEventLabels() { - assert (TOTAL_EVENT_LABELS - == (TOTAL_NEM_EVENT_TYPES - + TOTAL_LIST_BUILDER_EVENT_TYPES - + TOTAL_COALESCER_EVENT_TYPES)); - return EVENT_LABELS; - } - - /** - * @return if this event occurred in {@link ShadeListBuilder} - */ - static boolean isListBuilderEvent(@EventType int type) { - return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES); - } - - /** - * @return if this event occurred in {@link NotificationEntryManager} - */ - static boolean isNemEvent(@EventType int type) { - return isBetweenInclusive(type, TOTAL_LIST_BUILDER_EVENT_TYPES, - TOTAL_LIST_BUILDER_EVENT_TYPES + TOTAL_NEM_EVENT_TYPES); - } - - private static boolean isBetweenInclusive(int x, int a, int b) { - return x >= a && x <= b; - } - - @IntDef({ - // NotifListBuilder events: - WARN, - ON_BUILD_LIST, - START_BUILD_LIST, - DISPATCH_FINAL_LIST, - LIST_BUILD_COMPLETE, - PRE_GROUP_FILTER_INVALIDATED, - PROMOTER_INVALIDATED, - SECTION_INVALIDATED, - COMPARATOR_INVALIDATED, - PARENT_CHANGED, - FILTER_CHANGED, - PROMOTER_CHANGED, - PRE_RENDER_FILTER_INVALIDATED, - - // NotificationEntryManager events: - NOTIF_ADDED, - NOTIF_REMOVED, - NOTIF_UPDATED, - FILTER, - SORT, - FILTER_AND_SORT, - NOTIF_VISIBILITY_CHANGED, - LIFETIME_EXTENDED, - REMOVE_INTERCEPTED, - INFLATION_ABORTED, - INFLATED, - - // GroupCoalescer - COALESCED_EVENT, - EARLY_BATCH_EMIT, - EMIT_EVENT_BATCH - }) - @Retention(RetentionPolicy.SOURCE) - public @interface EventType {} - - private static final String[] EVENT_LABELS = - new String[]{ - // NotifListBuilder labels: - "Warning", - "OnBuildList", - "StartBuildList", - "DispatchFinalList", - "ListBuildComplete", - "FilterInvalidated", - "PromoterInvalidated", - "SectionInvalidated", - "ComparatorInvalidated", - "ParentChanged", - "FilterChanged", - "PromoterChanged", - "FinalFilterInvalidated", - "SectionerChanged", - - // NEM event labels: - "NotifAdded", - "NotifRemoved", - "NotifUpdated", - "Filter", - "Sort", - "FilterAndSort", - "NotifVisibilityChanged", - "LifetimeExtended", - "RemoveIntercepted", - "InflationAborted", - "Inflated", - - // GroupCoalescer labels: - "CoalescedEvent", - "EarlyBatchEmit", - "EmitEventBatch", - "BatchMaxTimeout" - }; - - private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length; - - /** - * Events related to {@link ShadeListBuilder} - */ - public static final int WARN = 0; - public static final int ON_BUILD_LIST = 1; - public static final int START_BUILD_LIST = 2; - public static final int DISPATCH_FINAL_LIST = 3; - public static final int LIST_BUILD_COMPLETE = 4; - public static final int PRE_GROUP_FILTER_INVALIDATED = 5; - public static final int PROMOTER_INVALIDATED = 6; - public static final int SECTION_INVALIDATED = 7; - public static final int COMPARATOR_INVALIDATED = 8; - public static final int PARENT_CHANGED = 9; - public static final int FILTER_CHANGED = 10; - public static final int PROMOTER_CHANGED = 11; - public static final int PRE_RENDER_FILTER_INVALIDATED = 12; - public static final int SECTION_CHANGED = 13; - private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14; - - /** - * Events related to {@link NotificationEntryManager} - */ - private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES; - public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX; - public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1; - public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2; - public static final int FILTER = NEM_EVENT_START_INDEX + 3; - public static final int SORT = NEM_EVENT_START_INDEX + 4; - public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5; - public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6; - public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7; - // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor} - public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8; - public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9; - public static final int INFLATED = NEM_EVENT_START_INDEX + 10; - private static final int TOTAL_NEM_EVENT_TYPES = 11; - - /** - * Events related to {@link GroupCoalescer} - */ - private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX - + TOTAL_NEM_EVENT_TYPES; - public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX; - public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1; - public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2; - public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3; - private static final int TOTAL_COALESCER_EVENT_TYPES = 3; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java deleted file mode 100644 index 299d628d0fd2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java +++ /dev/null @@ -1,115 +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.logging; - -import android.os.SystemProperties; -import android.service.notification.NotificationListenerService.Ranking; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.DumpController; -import com.android.systemui.log.SysuiLog; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Logs systemui notification events for debugging and triaging purposes. Logs are dumped in - * bugreports or on demand: - * adb shell dumpsys activity service com.android.systemui/.SystemUIService \ - * dependency DumpController NotifLog - */ -@Singleton -public class NotifLog extends SysuiLog<NotifEvent> { - private static final String TAG = "NotifLog"; - private static final boolean SHOW_NEM_LOGS = - SystemProperties.getBoolean("persist.sysui.log.notif.nem", true); - private static final boolean SHOW_LIST_BUILDER_LOGS = - SystemProperties.getBoolean("persist.sysui.log.notif.listbuilder", true); - - private static final int MAX_DOZE_DEBUG_LOGS = 400; - private static final int MAX_DOZE_LOGS = 50; - - private NotifEvent mRecycledEvent; - - @Inject - public NotifLog(DumpController dumpController) { - super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS); - } - - /** - * Logs a {@link NotifEvent} with a notification, ranking and message. - * Uses the last recycled event if available. - * @return true if successfully logged, else false - */ - public void log(@NotifEvent.EventType int eventType, - StatusBarNotification sbn, Ranking ranking, String msg) { - if (!mEnabled - || (NotifEvent.isListBuilderEvent(eventType) && !SHOW_LIST_BUILDER_LOGS) - || (NotifEvent.isNemEvent(eventType) && !SHOW_NEM_LOGS)) { - return; - } - - if (mRecycledEvent != null) { - mRecycledEvent = log(mRecycledEvent.init(eventType, sbn, ranking, msg)); - } else { - mRecycledEvent = log(new NotifEvent().init(eventType, sbn, ranking, msg)); - } - } - - /** - * Logs a {@link NotifEvent} with no extra information aside from the event type - */ - public void log(@NotifEvent.EventType int eventType) { - log(eventType, null, null, ""); - } - - /** - * Logs a {@link NotifEvent} with a message - */ - public void log(@NotifEvent.EventType int eventType, String msg) { - log(eventType, null, null, msg); - } - - /** - * Logs a {@link NotifEvent} with a entry - */ - public void log(@NotifEvent.EventType int eventType, NotificationEntry entry) { - log(eventType, entry.getSbn(), entry.getRanking(), ""); - } - - /** - * Logs a {@link NotifEvent} with a NotificationEntry and message - */ - public void log(@NotifEvent.EventType int eventType, NotificationEntry entry, String msg) { - log(eventType, entry.getSbn(), entry.getRanking(), msg); - } - - /** - * Logs a {@link NotifEvent} with a notification and message - */ - public void log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) { - log(eventType, sbn, null, msg); - } - - /** - * Logs a {@link NotifEvent} with a ranking and message - */ - public void log(@NotifEvent.EventType int eventType, Ranking ranking, String msg) { - log(eventType, null, ranking, msg); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt index 88b41471a063..fc221d43b3dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt @@ -93,8 +93,7 @@ class PeopleHubDataSourceImpl @Inject constructor( private val peopleHubManagerForUser = SparseArray<PeopleHubManager>() private val notificationEntryListener = object : NotificationEntryListener { - override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) = - addVisibleEntry(entry) + override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry) override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 50a20374fee5..3eac229af3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -33,16 +33,14 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; -import com.android.systemui.Dependency; +import com.android.systemui.Gefingerpoken; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.statusbar.phone.DoubleTapHelper; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} @@ -94,14 +92,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR = new PathInterpolator(0, 0, 0.5f, 1); private int mTintedRippleColor; - protected int mNormalRippleColor; - private final AccessibilityManager mAccessibilityManager; - private final DoubleTapHelper mDoubleTapHelper; + private int mNormalRippleColor; + private Gefingerpoken mTouchHandler; private boolean mDimmed; - protected int mBgTint = NO_COLOR; - private float mBgAlpha = 1f; + int mBgTint = NO_COLOR; /** * Flag to indicate that the notification has been touched once and the second touch will @@ -116,7 +112,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private Interpolator mCurrentAppearInterpolator; private Interpolator mCurrentAlphaInterpolator; - protected NotificationBackgroundView mBackgroundNormal; + NotificationBackgroundView mBackgroundNormal; private NotificationBackgroundView mBackgroundDimmed; private ObjectAnimator mBackgroundAnimator; private RectF mAppearAnimationRect = new RectF(); @@ -130,7 +126,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mLastInSection; private boolean mFirstInSection; private boolean mIsBelowSpeedBump; - private final FalsingManager mFalsingManager; private float mNormalBackgroundVisibilityAmount; private float mDimmedBackgroundFadeInAmount = -1; @@ -154,38 +149,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView */ private boolean mNeedsDimming; private int mDimmedAlpha; - private boolean mBlockNextTouch; private boolean mIsHeadsUpAnimation; private int mHeadsUpAddStartLocation; private float mHeadsUpLocation; private boolean mIsAppearing; private boolean mDismissed; private boolean mRefocusOnDismiss; + private OnDimmedListener mOnDimmedListener; + private AccessibilityManager mAccessibilityManager; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); - mFalsingManager = Dependency.get(FalsingManager.class); // TODO: inject into a controller. setClipChildren(false); setClipToPadding(false); updateColors(); - mAccessibilityManager = AccessibilityManager.getInstance(mContext); - - mDoubleTapHelper = new DoubleTapHelper(this, (active) -> { - if (active) { - makeActive(); - } else { - makeInactive(true /* animate */); - } - }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); initDimens(); } - public FalsingManager getFalsingManager() { - return mFalsingManager; - } - private void updateColors() { mNormalColor = mContext.getColor(R.color.notification_material_background_color); mTintedRippleColor = mContext.getColor( @@ -236,32 +218,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim); } - private final Runnable mTapTimeoutRunnable = new Runnable() { - @Override - public void run() { - makeInactive(true /* animate */); - } - }; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN - && disallowSingleClick(ev) && !isTouchExplorationEnabled()) { - if (!mActivated) { - return true; - } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { - mBlockNextTouch = true; - makeInactive(true /* animate */); - return true; - } + if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) { + return true; } return super.onInterceptTouchEvent(ev); } - private boolean isTouchExplorationEnabled() { - return mAccessibilityManager.isTouchExplorationEnabled(); - } - protected boolean disallowSingleClick(MotionEvent ev) { return false; } @@ -270,25 +235,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return false; } - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result; - if (mBlockNextTouch) { - mBlockNextTouch = false; - return false; - } - if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) { - boolean wasActivated = mActivated; - result = handleTouchEventDimmed(event); - if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { - removeCallbacks(mTapTimeoutRunnable); - } - } else { - result = super.onTouchEvent(event); - } - return result; - } - /** * @return whether this view is interactive and can be double tapped */ @@ -313,28 +259,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - public void setRippleAllowed(boolean allowed) { + void setRippleAllowed(boolean allowed) { mBackgroundNormal.setPressedAllowed(allowed); } - private boolean handleTouchEventDimmed(MotionEvent event) { - if (mNeedsDimming && !mDimmed) { - // We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple - super.onTouchEvent(event); - } - return mDoubleTapHelper.onTouchEvent(event, getActualHeight()); - } - - @Override - public boolean performClick() { - if (!mNeedsDimming || isTouchExplorationEnabled()) { - return super.performClick(); - } - return false; - } - - private void makeActive() { - mFalsingManager.onNotificationActive(); + void makeActive() { startActivateAnimation(false /* reverse */); mActivated = true; if (mOnActivatedListener != null) { @@ -388,19 +317,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.animate() .alpha(reverse ? 0f : 1f) .setInterpolator(alphaInterpolator) - .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float animatedFraction = animation.getAnimatedFraction(); - if (reverse) { - animatedFraction = 1.0f - animatedFraction; - } - setNormalBackgroundVisibilityAmount(animatedFraction); + .setUpdateListener(animation -> { + float animatedFraction = animation.getAnimatedFraction(); + if (reverse) { + animatedFraction = 1.0f - animatedFraction; } + setNormalBackgroundVisibilityAmount(animatedFraction); }) .setDuration(ACTIVATE_ANIMATION_LENGTH); } + @Override + public boolean performClick() { + if (!mNeedsDimming || (mAccessibilityManager != null + && mAccessibilityManager.isTouchExplorationEnabled())) { + return super.performClick(); + } + return false; + } + /** * Cancels the hotspot and makes the notification inactive. */ @@ -418,11 +353,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mOnActivatedListener != null) { mOnActivatedListener.onActivationReset(this); } - removeCallbacks(mTapTimeoutRunnable); } public void setDimmed(boolean dimmed, boolean fade) { mNeedsDimming = dimmed; + if (mOnDimmedListener != null) { + mOnDimmedListener.onSetDimmed(dimmed); + } dimmed &= isDimmable(); if (mDimmed != dimmed) { mDimmed = dimmed; @@ -439,13 +376,17 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return true; } + public boolean isDimmed() { + return mDimmed; + } + private void updateOutlineAlpha() { float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED; alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount); setOutlineAlpha(alpha); } - public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { + private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount; updateOutlineAlpha(); } @@ -473,14 +414,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView /** * Sets the tint color of the background */ - public void setTintColor(int color) { + protected void setTintColor(int color) { setTintColor(color, false); } /** * Sets the tint color of the background */ - public void setTintColor(int color, boolean animated) { + void setTintColor(int color, boolean animated) { if (color != mBgTint) { mBgTint = color; updateBackgroundTint(animated); @@ -562,13 +503,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mStartTint = mCurrentBackgroundTint; mTargetTint = color; mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, - animation.getAnimatedFraction()); - setBackgroundTintColor(newColor); - } + mBackgroundColorAnimator.addUpdateListener(animation -> { + int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, + animation.getAnimatedFraction()); + setBackgroundTintColor(newColor); }); mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); @@ -643,11 +581,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } protected void updateBackgroundAlpha(float transformationAmount) { - mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; + float bgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; if (mDimmedBackgroundFadeInAmount != -1) { - mBgAlpha *= mDimmedBackgroundFadeInAmount; + bgAlpha *= mDimmedBackgroundFadeInAmount; } - mBackgroundDimmed.setAlpha(mBgAlpha); + mBackgroundDimmed.setAlpha(bgAlpha); } protected void resetBackgroundAlpha() { @@ -671,7 +609,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setVisibility(View.INVISIBLE); mBackgroundNormal.setVisibility(View.VISIBLE); mBackgroundNormal.setAlpha(1f); - removeCallbacks(mTapTimeoutRunnable); // make in inactive to avoid it sticking around active makeInactive(false /* animate */); } @@ -783,14 +720,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimator.setInterpolator(Interpolators.LINEAR); mAppearAnimator.setDuration( (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); - mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mAppearAnimationFraction = (float) animation.getAnimatedValue(); - updateAppearAnimationAlpha(); - updateAppearRect(); - invalidate(); - } + mAppearAnimator.addUpdateListener(animation -> { + mAppearAnimationFraction = (float) animation.getAnimatedValue(); + updateAppearAnimationAlpha(); + updateAppearRect(); + invalidate(); }); if (animationListener != null) { mAppearAnimator.addListener(animationListener); @@ -921,7 +855,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView getCurrentBackgroundRadiusBottom()); } - protected void applyBackgroundRoundness(float topRadius, float bottomRadius) { + private void applyBackgroundRoundness(float topRadius, float bottomRadius) { mBackgroundDimmed.setRoundness(topRadius, bottomRadius); mBackgroundNormal.setRoundness(topRadius, bottomRadius); } @@ -963,7 +897,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - protected int getRippleColor() { + private int getRippleColor() { if (mBgTint != 0) { return mTintedRippleColor; } else { @@ -1010,10 +944,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mOnActivatedListener = onActivatedListener; } - public boolean hasSameBgColor(ActivatableNotificationView otherView) { - return calculateBgColor() == otherView.calculateBgColor(); - } - @Override public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation) { @@ -1071,8 +1001,24 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return mRefocusOnDismiss || isAccessibilityFocused(); } + void setTouchHandler(Gefingerpoken touchHandler) { + mTouchHandler = touchHandler; + } + + void setOnDimmedListener(OnDimmedListener onDimmedListener) { + mOnDimmedListener = onDimmedListener; + } + + public void setAccessibilityManager(AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; + } + public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); } + + interface OnDimmedListener { + void onSetDimmed(boolean dimmed); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java index 18993ffec357..8465658079f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java @@ -16,9 +16,13 @@ package com.android.systemui.statusbar.notification.row; +import android.view.MotionEvent; +import android.view.View; import android.view.accessibility.AccessibilityManager; +import com.android.systemui.Gefingerpoken; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.phone.DoubleTapHelper; import javax.inject.Inject; @@ -27,21 +31,102 @@ import javax.inject.Inject; */ public class ActivatableNotificationViewController { private final ActivatableNotificationView mView; + private final ExpandableOutlineViewController mExpandableOutlineViewController; private final AccessibilityManager mAccessibilityManager; private final FalsingManager mFalsingManager; + private DoubleTapHelper mDoubleTapHelper; + private boolean mNeedsDimming; + + private TouchHandler mTouchHandler = new TouchHandler(); @Inject public ActivatableNotificationViewController(ActivatableNotificationView view, + ExpandableOutlineViewController expandableOutlineViewController, AccessibilityManager accessibilityManager, FalsingManager falsingManager) { mView = view; + mExpandableOutlineViewController = expandableOutlineViewController; mAccessibilityManager = accessibilityManager; mFalsingManager = falsingManager; + + mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() { + @Override + public void onActivated(ActivatableNotificationView view) { + mFalsingManager.onNotificationActive(); + } + + @Override + public void onActivationReset(ActivatableNotificationView view) { + } + }); } /** * Initialize the controller, setting up handlers and other behavior. */ public void init() { + mExpandableOutlineViewController.init(); + mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> { + if (active) { + mView.makeActive(); + mFalsingManager.onNotificationActive(); + } else { + mView.makeInactive(true /* animate */); + } + }, mView::performClick, mView::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + mView.setOnTouchListener(mTouchHandler); + mView.setTouchHandler(mTouchHandler); + mView.setOnDimmedListener(dimmed -> { + mNeedsDimming = dimmed; + }); + mView.setAccessibilityManager(mAccessibilityManager); + } + + class TouchHandler implements Gefingerpoken, View.OnTouchListener { + private boolean mBlockNextTouch; + + @Override + public boolean onTouch(View v, MotionEvent ev) { + boolean result; + if (mBlockNextTouch) { + mBlockNextTouch = false; + return true; + } + if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled() + && mView.isInteractive()) { + if (mNeedsDimming && !mView.isDimmed()) { + // We're actually dimmed, but our content isn't dimmable, + // let's ensure we have a ripple + return false; + } + result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight()); + } else { + return false; + } + return result; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN + && mView.disallowSingleClick(ev) + && !mAccessibilityManager.isTouchExplorationEnabled()) { + if (!mView.isActivated()) { + return true; + } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { + mBlockNextTouch = true; + mView.makeInactive(true /* animate */); + return true; + } + } + return false; + } + /** + * Use {@link #onTouch(View, MotionEvent) instead}. + */ + @Override + public boolean onTouchEvent(MotionEvent ev) { + return false; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java new file mode 100644 index 000000000000..1cf6b4f2321c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; + +/** + * A {@link BindRequester} is a general superclass for something that notifies + * {@link NotifBindPipeline} when it needs it to kick off a bind run. + */ +public abstract class BindRequester { + private @Nullable BindRequestListener mBindRequestListener; + + /** + * Notifies the listener that some parameters/state has changed for some notification and that + * content needs to be bound again. + * + * The caller can also specify a callback for when the entire bind pipeline completes, i.e. + * when the change is fully propagated to the final view. The caller can cancel this + * callback with the returned cancellation signal. + * + * @param callback callback after bind completely finishes + * @return cancellation signal to cancel callback + */ + public final CancellationSignal requestRebind( + @NonNull NotificationEntry entry, + @Nullable BindCallback callback) { + CancellationSignal signal = new CancellationSignal(); + if (mBindRequestListener != null) { + mBindRequestListener.onBindRequest(entry, signal, callback); + } + return signal; + } + + final void setBindRequestListener(BindRequestListener listener) { + mBindRequestListener = listener; + } + + /** + * Listener interface for when content needs to be bound again. + */ + public interface BindRequestListener { + + /** + * Called when {@link #requestRebind} is called. + * + * @param entry notification that has outdated content + * @param signal cancellation signal to cancel callback + * @param callback callback after content is fully updated + */ + void onBindRequest( + @NonNull NotificationEntry entry, + @NonNull CancellationSignal signal, + @Nullable BindCallback callback); + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java new file mode 100644 index 000000000000..29447caa1240 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import android.annotation.MainThread; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.util.Map; + +/** + * A {@link BindStage} is an abstraction for a unit of work in inflating/binding/unbinding + * views to a notification. Used by {@link NotifBindPipeline}. + * + * Clients may also use {@link #getStageParams} to provide parameters for this stage for a given + * notification and request a rebind. + * + * @param <Params> params to do this stage + */ +@MainThread +public abstract class BindStage<Params> extends BindRequester { + + private Map<NotificationEntry, Params> mContentParams = new ArrayMap<>(); + + /** + * Execute the stage asynchronously. + * + * @param row notification top-level view to bind views to + * @param callback callback after stage finishes + */ + protected abstract void executeStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, + @NonNull StageCallback callback); + + /** + * Abort the stage if in progress. + * + * @param row notification top-level view to bind views to + */ + protected abstract void abortStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row); + + /** + * Get the stage parameters for the entry. Clients should use this to modify how the stage + * handles the notification content. + */ + public final Params getStageParams(@NonNull NotificationEntry entry) { + Params params = mContentParams.get(entry); + if (params == null) { + throw new IllegalStateException( + String.format("Entry does not have any stage parameters. key: %s", + entry.getKey())); + } + return params; + } + + /** + * Create a params entry for the notification for this stage. + */ + final void createStageParams(@NonNull NotificationEntry entry) { + mContentParams.put(entry, newStageParams()); + } + + /** + * Delete params entry for notification. + */ + final void deleteStageParams(@NonNull NotificationEntry entry) { + mContentParams.remove(entry); + } + + /** + * Create a new, empty stage params object. + */ + protected abstract Params newStageParams(); + + /** + * Interface for callback. + */ + interface StageCallback { + /** + * Callback for when the stage is complete. + */ + void onStageFinished(NotificationEntry entry); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt new file mode 100644 index 000000000000..373457d4e336 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt @@ -0,0 +1,43 @@ +/* +* Copyright (C) 2020 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.android.systemui.statusbar.notification.row + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import android.widget.TextView +import com.android.systemui.R +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.collection.NotificationEntry + +class DungeonRow(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { + var entry: NotificationEntry? = null + set(value) { + field = value + update() + } + + private fun update() { + (findViewById(R.id.app_name) as TextView).apply { + text = entry?.row?.appName + } + + (findViewById(R.id.icon) as StatusBarIconView).apply { + set(entry?.icon?.statusBarIcon) + } + } +} 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 b71bedaca707..551731824570 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 @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -44,7 +42,6 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; @@ -74,11 +71,11 @@ import com.android.internal.widget.CachingIconView; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; @@ -88,8 +85,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationCounters; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -127,14 +122,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); - /** - * Content views that must be inflated at all times. - */ - @InflationFlag - static final int REQUIRED_INFLATION_FLAGS = - FLAG_CONTENT_VIEW_CONTRACTED - | FLAG_CONTENT_VIEW_EXPANDED; - private boolean mUpdateBackgroundOnUpdate; private boolean mNotificationTranslationFinished = false; @@ -149,7 +136,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private StatusBarStateController mStatusbarStateController; private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; - private NotificationRowContentBinder mNotificationContentBinder; + private RowContentBindStage mRowContentBindStage; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mMaxHeadsUpHeightBeforeN; @@ -211,6 +198,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationGuts mGuts; private NotificationEntry mEntry; private String mAppName; + private FalsingManager mFalsingManager; /** * Whether or not the notification is using the heads up view and should peek from the top. @@ -244,10 +232,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private ExpandableNotificationRow mNotificationParent; private OnExpandClickListener mOnExpandClickListener; private View.OnClickListener mOnAppOpsClickListener; - private InflationCallback mInflationCallback; private boolean mIsChildInGroup; - private @InflationFlag int mInflationFlags = REQUIRED_INFLATION_FLAGS; - private final BindParams mBindParams = new BindParams(); // Listener will be called when receiving a long click event. // Use #setLongPressPosition to optionally assign positional data with the long press. @@ -460,24 +445,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Inflate views based off the inflation flags set. Inflation happens asynchronously. - */ - public void inflateViews() { - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, - false /* forceInflate */, mInflationCallback); - } - - /** * Marks a content view as freeable, setting it so that future inflations do not reinflate * and ensuring that the view is freed when it is safe to remove. * + * TODO: This should be moved to the respective coordinator and call + * {@link RowContentBindParams#freeContentViews} directly after disappear animation + * finishes instead of depending on binding API to know when it's "safe". + * * @param inflationFlag flag corresponding to the content view to be freed */ public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { // View should not be reinflated in the future - clearInflationFlags(inflationFlag); - Runnable freeViewRunnable = - () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag); + Runnable freeViewRunnable = () -> { + // Possible for notification to be removed after free request. + if (!isRemoved()) { + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.freeContentViews(inflationFlag); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); + } + }; switch (inflationFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, @@ -492,35 +478,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Set flags for content views that should be inflated - * - * @param flags flags to inflate - */ - public void setInflationFlags(@InflationFlag int flags) { - mInflationFlags |= flags; - } - - /** - * Clear flags for content views that should not be inflated - * - * @param flags flags that should not be inflated - */ - public void clearInflationFlags(@InflationFlag int flags) { - mInflationFlags &= ~flags; - mInflationFlags |= REQUIRED_INFLATION_FLAGS; - } - - /** - * Whether or not a content view should be inflated. - * - * @param flag the flag corresponding to the content view - * @return true if the flag is set, false otherwise - */ - public boolean isInflationFlagSet(@InflationFlag int flag) { - return ((mInflationFlags & flag) != 0); - } - - /** * Caches whether or not this row contains a system notification. Note, this is only cached * once per notification as the packageInfo can't technically change for a notification row. */ @@ -745,6 +702,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.setRemoteInputController(r); } + + String getAppName() { + return mAppName; + } + public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } @@ -833,13 +795,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } mNotificationParent = isChildInGroup ? parent : null; mPrivateLayout.setIsChildInGroup(isChildInGroup); - mBindParams.isChildInGroup = isChildInGroup; + // TODO: Move inflation logic out of this call if (mIsChildInGroup != isChildInGroup) { mIsChildInGroup = isChildInGroup; if (mIsLowPriority) { - int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams, - false /* forceInflate */, mInflationCallback); + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.setUseLowPriority(mIsLowPriority); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } } resetBackgroundAlpha(); @@ -1125,20 +1087,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mEntry.setInitializationTime(SystemClock.elapsedRealtime()); - Dependency.get(PluginManager.class).addPluginListener(this, - NotificationMenuRowPlugin.class, false /* Allow multiple */); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(this); - } - - @Override public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null; if (existed) { @@ -1238,8 +1186,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reInflateViews(); } mEntry.getSbn().clearPackageContext(); - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, - true /* forceInflate */, mInflationCallback); + // TODO: Move content inflation logic out of this call + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.setNeedsReinflation(true); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } @Override @@ -1475,7 +1425,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsBlockingHelperShowing && mNotificationTranslationFinished; } - public void setOnDismissRunnable(Runnable onDismissRunnable) { + void setOnDismissRunnable(Runnable onDismissRunnable) { mOnDismissRunnable = onDismissRunnable; } @@ -1593,7 +1543,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void setIsLowPriority(boolean isLowPriority) { mIsLowPriority = isLowPriority; mPrivateLayout.setIsLowPriority(isLowPriority); - mBindParams.isLowPriority = mIsLowPriority; if (mChildrenContainer != null) { mChildrenContainer.setIsLowPriority(isLowPriority); } @@ -1603,36 +1552,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsLowPriority; } - public void setUseIncreasedCollapsedHeight(boolean use) { + public void setUsesIncreasedCollapsedHeight(boolean use) { mUseIncreasedCollapsedHeight = use; - mBindParams.usesIncreasedHeight = use; } - public void setUseIncreasedHeadsUpHeight(boolean use) { + public void setUsesIncreasedHeadsUpHeight(boolean use) { mUseIncreasedHeadsUpHeight = use; - mBindParams.usesIncreasedHeadsUpHeight = use; - } - - /** - * Set callback for notification content inflation - * - * @param callback inflation callback - */ - public void setInflationCallback(InflationCallback callback) { - mInflationCallback = callback; } public void setNeedsRedaction(boolean needsRedaction) { + // TODO: Move inflation logic out of this call and remove this method if (mNeedsRedaction != needsRedaction) { mNeedsRedaction = needsRedaction; + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); if (needsRedaction) { - setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, - mBindParams, false /* forceInflate */, mInflationCallback); + params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); } else { - clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - freeContentViewWhenSafe(FLAG_CONTENT_VIEW_PUBLIC); + params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC); } + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } } @@ -1645,7 +1583,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); - mMediaManager = Dependency.get(NotificationMediaManager.class); initDimens(); } @@ -1659,8 +1596,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView KeyguardBypassController bypassController, NotificationGroupManager groupManager, HeadsUpManager headsUpManager, - NotificationRowContentBinder rowContentBinder, - OnExpandClickListener onExpandClickListener) { + RowContentBindStage rowContentBindStage, + OnExpandClickListener onExpandClickListener, + NotificationMediaManager notificationMediaManager, + OnAppOpsClickListener onAppOpsClickListener, + FalsingManager falsingManager, + StatusBarStateController statusBarStateController) { mAppName = appName; if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.setAppName(mAppName); @@ -1671,11 +1612,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mGroupManager = groupManager; mPrivateLayout.setGroupManager(groupManager); mHeadsUpManager = headsUpManager; - mNotificationContentBinder = rowContentBinder; + mRowContentBindStage = rowContentBindStage; mOnExpandClickListener = onExpandClickListener; - } - - public void setStatusBarStateController(StatusBarStateController statusBarStateController) { + mMediaManager = notificationMediaManager; + setAppOpsOnClickListener(onAppOpsClickListener); + mFalsingManager = falsingManager; mStatusbarStateController = statusBarStateController; } @@ -1767,7 +1708,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mOnAppOpsClickListener; } - public void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { + void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { mOnAppOpsClickListener = v -> { createMenu(); NotificationMenuRowPlugin provider = getProvider(); @@ -2236,7 +2177,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @param allowChildExpansion whether a call to this method allows expanding children */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { - getFalsingManager().setNotificationExpanded(); + mFalsingManager.setNotificationExpanded(); if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion && !mChildrenContainer.showingAsLowPriority()) { final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java new file mode 100644 index 000000000000..39fab439ad07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.dagger.AppName; +import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable; +import com.android.systemui.statusbar.notification.row.dagger.NotificationKey; +import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.time.SystemClock; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Controller for {@link ExpandableNotificationRow}. + */ +@NotificationRowScope +public class ExpandableNotificationRowController { + private final ExpandableNotificationRow mView; + private final ActivatableNotificationViewController mActivatableNotificationViewController; + private final NotificationMediaManager mMediaManager; + private final PluginManager mPluginManager; + private final SystemClock mClock; + private final String mAppName; + private final String mNotificationKey; + private final KeyguardBypassController mKeyguardBypassController; + private final NotificationGroupManager mNotificationGroupManager; + private final RowContentBindStage mRowContentBindStage; + private final NotificationLogger mNotificationLogger; + private final HeadsUpManager mHeadsUpManager; + private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener; + private final StatusBarStateController mStatusBarStateController; + private final NotificationRowContentBinder.InflationCallback mInflationCallback; + + private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = + this::logNotificationExpansion; + private final ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; + private final NotificationGutsManager mNotificationGutsManager; + private Runnable mOnDismissRunnable; + private final FalsingManager mFalsingManager; + private final boolean mAllowLongPress; + + @Inject + public ExpandableNotificationRowController(ExpandableNotificationRow view, + ActivatableNotificationViewController activatableNotificationViewController, + NotificationMediaManager mediaManager, PluginManager pluginManager, + SystemClock clock, @AppName String appName, @NotificationKey String notificationKey, + KeyguardBypassController keyguardBypassController, + NotificationGroupManager notificationGroupManager, + RowContentBindStage rowContentBindStage, + NotificationLogger notificationLogger, HeadsUpManager headsUpManager, + ExpandableNotificationRow.OnExpandClickListener onExpandClickListener, + StatusBarStateController statusBarStateController, + NotificationRowContentBinder.InflationCallback inflationCallback, + NotificationGutsManager notificationGutsManager, + @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, + @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) { + mView = view; + mActivatableNotificationViewController = activatableNotificationViewController; + mMediaManager = mediaManager; + mPluginManager = pluginManager; + mClock = clock; + mAppName = appName; + mNotificationKey = notificationKey; + mKeyguardBypassController = keyguardBypassController; + mNotificationGroupManager = notificationGroupManager; + mRowContentBindStage = rowContentBindStage; + mNotificationLogger = notificationLogger; + mHeadsUpManager = headsUpManager; + mOnExpandClickListener = onExpandClickListener; + mStatusBarStateController = statusBarStateController; + mInflationCallback = inflationCallback; + mNotificationGutsManager = notificationGutsManager; + mOnDismissRunnable = onDismissRunnable; + mOnAppOpsClickListener = mNotificationGutsManager::openGuts; + mAllowLongPress = allowLongPress; + mFalsingManager = falsingManager; + } + + /** + * Initialize the controller. + */ + public void init() { + mActivatableNotificationViewController.init(); + mView.initialize( + mAppName, + mNotificationKey, + mExpansionLogger, + mKeyguardBypassController, + mNotificationGroupManager, + mHeadsUpManager, + mRowContentBindStage, + mOnExpandClickListener, + mMediaManager, + mOnAppOpsClickListener, + mFalsingManager, + mStatusBarStateController + ); + mView.setOnDismissRunnable(mOnDismissRunnable); + mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (mAllowLongPress) { + mView.setLongPressListener(mNotificationGutsManager::openGuts); + } + if (ENABLE_REMOTE_INPUT) { + mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + mPluginManager.addPluginListener(mView, + NotificationMenuRowPlugin.class, false /* Allow multiple */); + } + + @Override + public void onViewDetachedFromWindow(View v) { + mPluginManager.removePluginListener(mView); + } + }); + } + + private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mNotificationLogger.onExpansionChanged(key, userAction, expanded); + } + + /** */ + public void setOnDismissRunnable(Runnable onDismissRunnable) { + mOnDismissRunnable = onDismissRunnable; + mView.setOnDismissRunnable(onDismissRunnable); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 28f4136d5ecb..049cafa4ccde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -93,7 +93,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { } else { Path clipPath = getClipPath(false /* ignoreTranslation */); if (clipPath != null) { - outline.setConvexPath(clipPath); + outline.setPath(clipPath); } } outline.setAlpha(mOutlineAlpha); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java new file mode 100644 index 000000000000..75c9d1e6f2fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableOutlineView}. + */ +public class ExpandableOutlineViewController { + private final ExpandableOutlineView mView; + private final ExpandableViewController mExpandableViewController; + + @Inject + public ExpandableOutlineViewController(ExpandableOutlineView view, + ExpandableViewController expandableViewController) { + mView = view; + mExpandableViewController = expandableViewController; + } + + /** + * Initialize the controller. + */ + public void init() { + mExpandableViewController.init(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java new file mode 100644 index 000000000000..e14ca8c4e590 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableView}. + */ +public class ExpandableViewController { + private final ExpandableView mView; + + @Inject + public ExpandableViewController(ExpandableView view) { + mView = view; + } + + /** + * Initialize the controller. + */ + public void init() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ForegroundServiceDungeonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ForegroundServiceDungeonView.kt new file mode 100644 index 000000000000..17396ad31ba2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ForegroundServiceDungeonView.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.content.Context +import android.util.AttributeSet +import android.view.View + +import com.android.systemui.R + +class ForegroundServiceDungeonView(context: Context, attrs: AttributeSet) + : StackScrollerDecorView(context, attrs) { + override fun findContentView(): View? { + return findViewById(R.id.foreground_service_dungeon) + } + + override fun findSecondaryView(): View? { + return null + } + + override fun setVisible(visible: Boolean, animate: Boolean) { + // Visibility is controlled by the ForegroundServiceSectionController + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java new file mode 100644 index 000000000000..af2d0844412a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import android.util.ArrayMap; +import android.util.ArraySet; +import android.widget.FrameLayout; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * {@link NotifBindPipeline} is responsible for converting notifications from their data form to + * their actual inflated views. It is essentially a control class that composes notification view + * binding logic (i.e. {@link BindStage}) in response to explicit bind requests. At the end of the + * pipeline, the notification's bound views are guaranteed to be correct and up-to-date, and any + * registered callbacks will be called. + * + * The pipeline ensures that a notification's top-level view and its content views are bound. + * Currently, a notification's top-level view, the {@link ExpandableNotificationRow} is essentially + * just a {@link FrameLayout} for various different content views that are switched in and out as + * appropriate. These include a contracted view, expanded view, heads up view, and sensitive view on + * keyguard. See {@link InflationFlag}. These content views themselves can have child views added + * on depending on different factors. For example, notification actions and smart replies are views + * that are dynamically added to these content views after they're inflated. Finally, aside from + * the app provided content views, System UI itself also provides some content views that are shown + * occasionally (e.g. {@link NotificationGuts}). Many of these are business logic specific views + * and the requirements surrounding them may change over time, so the pipeline must handle + * composing the logic as necessary. + * + * Note that bind requests do not only occur from add/updates from updates from the app. For + * example, the user may make changes to device settings (e.g. sensitive notifications on lock + * screen) or we may want to make certain optimizations for the sake of memory or performance (e.g + * freeing views when not visible). Oftentimes, we also need to wait for these changes to complete + * before doing something else (e.g. moving a notification to the top of the screen to heads up). + * The pipeline thus handles bind requests from across the system and provides a way for + * requesters to know when the change is propagated to the view. + * + * Right now, we only support one attached {@link BindStage} which just does all the binding but we + * should eventually support multiple stages once content inflation is made more modular. + * In particular, row inflation/binding, which is handled by {@link NotificationRowBinder} should + * probably be moved here in the future as a stage. Right now, the pipeline just manages content + * views and assumes that a row is given to it when it's inflated. + */ +@MainThread +@Singleton +public final class NotifBindPipeline { + private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>(); + private BindStage mStage; + + @Inject + NotifBindPipeline(NotificationEntryManager entryManager) { + entryManager.addNotificationEntryListener(mEntryListener); + } + + /** + * Set the bind stage for binding notification row content. + */ + public void setStage( + BindStage stage) { + mStage = stage; + mStage.setBindRequestListener(this::onBindRequested); + } + + /** + * Start managing the row's content for a given notification. + */ + public void manageRow( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + final BindEntry bindEntry = getBindEntry(entry); + bindEntry.row = row; + if (bindEntry.invalidated) { + startPipeline(entry); + } + } + + private void onBindRequested( + @NonNull NotificationEntry entry, + @NonNull CancellationSignal signal, + @Nullable BindCallback callback) { + final BindEntry bindEntry = getBindEntry(entry); + if (bindEntry == null) { + // Invalidating views for a notification that is not active. + return; + } + + bindEntry.invalidated = true; + + // Put in new callback. + if (callback != null) { + final Set<BindCallback> callbacks = bindEntry.callbacks; + callbacks.add(callback); + signal.setOnCancelListener(() -> callbacks.remove(callback)); + } + + startPipeline(entry); + } + + /** + * Run the pipeline for the notification, ensuring all views are bound when finished. Call all + * callbacks when the run finishes. If a run is already in progress, it is restarted. + */ + private void startPipeline(NotificationEntry entry) { + if (mStage == null) { + throw new IllegalStateException("No stage was ever set on the pipeline"); + } + + final BindEntry bindEntry = mBindEntries.get(entry); + final ExpandableNotificationRow row = bindEntry.row; + if (row == null) { + // Row is not managed yet but may be soon. Stop for now. + return; + } + + mStage.abortStage(entry, row); + mStage.executeStage(entry, row, (en) -> onPipelineComplete(en)); + } + + private void onPipelineComplete(NotificationEntry entry) { + final BindEntry bindEntry = getBindEntry(entry); + + bindEntry.invalidated = false; + + final Set<BindCallback> callbacks = bindEntry.callbacks; + for (BindCallback cb : callbacks) { + cb.onBindFinished(entry); + } + callbacks.clear(); + } + + //TODO: Move this to onManageEntry hook when we split that from add/remove + private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + @Override + public void onPendingEntryAdded(NotificationEntry entry) { + mBindEntries.put(entry, new BindEntry()); + mStage.createStageParams(entry); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, + @Nullable NotificationVisibility visibility, + boolean removedByUser) { + BindEntry bindEntry = mBindEntries.remove(entry); + ExpandableNotificationRow row = bindEntry.row; + if (row != null) { + mStage.abortStage(entry, row); + } + mStage.deleteStageParams(entry); + } + }; + + private @NonNull BindEntry getBindEntry(NotificationEntry entry) { + final BindEntry bindEntry = mBindEntries.get(entry); + if (bindEntry == null) { + throw new IllegalStateException( + String.format("Attempting bind on an inactive notification. key: %s", + entry.getKey())); + } + return bindEntry; + } + + /** + * Interface for bind callback. + */ + public interface BindCallback { + /** + * Called when all views are fully bound on the notification. + */ + void onBindFinished(NotificationEntry entry); + } + + private class BindEntry { + public ExpandableNotificationRow row; + public final Set<BindCallback> callbacks = new ArraySet<>(); + public boolean invalidated; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java new file mode 100644 index 000000000000..7754991557ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import javax.inject.Inject; + +/** + * Initialize {@link NotifBindPipeline} with all its mandatory stages and dynamically added stages. + * + * In the future, coordinators should be able to register their own {@link BindStage} to the + * {@link NotifBindPipeline}. + */ +public class NotifBindPipelineInitializer { + NotifBindPipeline mNotifBindPipeline; + RowContentBindStage mRowContentBindStage; + + @Inject + NotifBindPipelineInitializer( + NotifBindPipeline pipeline, + RowContentBindStage stage) { + mNotifBindPipeline = pipeline; + mRowContentBindStage = stage; + // TODO: Inject coordinators and allow them to add BindStages in initialize + } + + /** + * Hooks up stages to the pipeline. + */ + public void initialize() { + // Mandatory bind stages + mNotifBindPipeline.setStage(mRowContentBindStage); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java index a6e5c2b79968..b62dfa8a20f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java @@ -22,10 +22,9 @@ import android.widget.RemoteViews; import androidx.annotation.Nullable; -import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import java.util.Map; @@ -40,8 +39,8 @@ public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { new ArrayMap<>(); @Inject - NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) { - entryManager.addNotificationEntryListener(mEntryListener); + NotifRemoteViewCacheImpl(CommonNotifCollection collection) { + collection.addCollectionListener(mCollectionListener); } @Override @@ -93,17 +92,14 @@ public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { contentViews.clear(); } - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { + private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() { @Override - public void onPendingEntryAdded(NotificationEntry entry) { + public void onEntryInit(NotificationEntry entry) { mNotifCachedContentViews.put(entry, new SparseArray<>()); } @Override - public void onEntryRemoved( - NotificationEntry entry, - @Nullable NotificationVisibility visibility, - boolean removedByUser) { + public void onEntryCleanUp(NotificationEntry entry) { mNotifCachedContentViews.remove(entry); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index e1a6747b5398..566da65e37f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -68,7 +68,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotifRemoteViewCache mRemoteViewCache; @Inject - public NotificationContentInflater( + NotificationContentInflater( NotifRemoteViewCache remoteViewCache, NotificationRemoteInputManager remoteInputManager) { mRemoteViewCache = remoteViewCache; @@ -575,7 +575,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(entry, reInflateFlags); + endListener.onAsyncInflationFinished(entry); } return true; } @@ -641,7 +641,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final boolean mUsesIncreasedHeight; private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; - private @InflationFlag int mReInflateFlags; + private final @InflationFlag int mReInflateFlags; private final NotifRemoteViewCache mRemoteViewCache; private ExpandableNotificationRow mRow; private Exception mError; @@ -739,25 +739,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder } @Override - public void supersedeTask(InflationTask task) { - if (task instanceof AsyncInflationTask) { - // We want to inflate all flags of the previous task as well - mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags; - } - } - - @Override public void handleInflationException(NotificationEntry entry, Exception e) { handleError(e); } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { mEntry.onInflationTaskFinished(); mRow.onNotificationUpdated(); if (mCallback != null) { - mCallback.onAsyncInflationFinished(mEntry, inflatedFlags); + mCallback.onAsyncInflationFinished(mEntry); } // Notify the resolver that the inflation task has finished, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 6045524f30b6..a0af4ace05b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -23,14 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_UNBUBBLE; - import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; @@ -69,11 +61,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.phone.ShadeController; import java.lang.annotation.Retention; import java.util.Arrays; @@ -92,6 +86,7 @@ public class NotificationConversationInfo extends LinearLayout implements ShortcutManager mShortcutManager; private PackageManager mPm; private VisualStabilityManager mVisualStabilityManager; + private ShadeController mShadeController; private String mPackageName; private String mAppName; @@ -103,24 +98,30 @@ public class NotificationConversationInfo extends LinearLayout implements private NotificationEntry mEntry; private StatusBarNotification mSbn; private boolean mIsDeviceProvisioned; - private int mStartingChannelImportance; private boolean mStartedAsBubble; private boolean mIsBubbleable; - // TODO: remove when launcher api works - @VisibleForTesting - boolean mShowHomeScreen = false; - private @UpdateChannelRunnable.Action int mSelectedAction = -1; + private @Action int mSelectedAction = -1; private OnSnoozeClickListener mOnSnoozeClickListener; private OnSettingsClickListener mOnSettingsClickListener; - private OnAppSettingsClickListener mAppSettingsClickListener; private NotificationGuts mGutsContainer; private BubbleController mBubbleController; @VisibleForTesting boolean mSkipPost = false; + @Retention(SOURCE) + @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE}) + private @interface Action {} + static final int ACTION_BUBBLE = 0; + static final int ACTION_HOME = 1; + static final int ACTION_FAVORITE = 2; + static final int ACTION_SNOOZE = 3; + static final int ACTION_MUTE = 4; + static final int ACTION_SETTINGS = 5; + static final int ACTION_UNBUBBLE = 6; + private OnClickListener mOnBubbleClick = v -> { mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE; if (mStartedAsBubble) { @@ -136,12 +137,14 @@ public class NotificationConversationInfo extends LinearLayout implements private OnClickListener mOnHomeClick = v -> { mSelectedAction = ACTION_HOME; mShortcutManager.requestPinShortcut(mShortcutInfo, null); + mShadeController.animateCollapsePanels(); closeControls(v, true); }; private OnClickListener mOnFavoriteClick = v -> { mSelectedAction = ACTION_FAVORITE; - closeControls(v, true); + updateChannel(); + }; private OnClickListener mOnSnoozeClick = v -> { @@ -151,13 +154,8 @@ public class NotificationConversationInfo extends LinearLayout implements }; private OnClickListener mOnMuteClick = v -> { - mSelectedAction = ACTION_MUTE; - closeControls(v, true); - }; - - private OnClickListener mOnDemoteClick = v -> { - mSelectedAction = ACTION_DEMOTE; - closeControls(v, true); + mSelectedAction = ACTION_MUTE; + updateChannel(); }; public NotificationConversationInfo(Context context, AttributeSet attrs) { @@ -197,15 +195,14 @@ public class NotificationConversationInfo extends LinearLayout implements mEntry = entry; mSbn = entry.getSbn(); mPm = pm; - mAppSettingsClickListener = onAppSettingsClick; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; mNotificationChannel = notificationChannel; - mStartingChannelImportance = mNotificationChannel.getImportance(); mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; mOnSnoozeClickListener = onSnoozeClickListener; + mShadeController = Dependency.get(ShadeController.class); mShortcutManager = shortcutManager; mLauncherApps = launcherApps; @@ -251,9 +248,6 @@ public class NotificationConversationInfo extends LinearLayout implements mNotificationChannel = mINotificationManager.getConversationNotificationChannel( mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName, mNotificationChannel.getId(), false, mConversationId); - - // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a - // time } catch (RemoteException e) { Slog.e(TAG, "Could not create conversation channel", e); } @@ -274,40 +268,24 @@ public class NotificationConversationInfo extends LinearLayout implements Button home = findViewById(R.id.home); home.setOnClickListener(mOnHomeClick); - home.setVisibility(mShowHomeScreen && mShortcutInfo != null + home.setVisibility(mShortcutInfo != null && mShortcutManager.isRequestPinShortcutSupported() ? VISIBLE : GONE); - Button favorite = findViewById(R.id.fave); + View favorite = findViewById(R.id.fave); favorite.setOnClickListener(mOnFavoriteClick); - if (mNotificationChannel.canBypassDnd()) { - favorite.setText(R.string.notification_conversation_unfavorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star), null, null, null); - } else { - favorite.setText(R.string.notification_conversation_favorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star_border), null, null, null); - } Button snooze = findViewById(R.id.snooze); snooze.setOnClickListener(mOnSnoozeClick); - Button mute = findViewById(R.id.mute); + View mute = findViewById(R.id.mute); mute.setOnClickListener(mOnMuteClick); - if (mStartingChannelImportance >= IMPORTANCE_DEFAULT - || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) { - mute.setText(R.string.notification_conversation_mute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null); - } else { - mute.setText(R.string.notification_conversation_unmute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null); - } - ImageButton demote = findViewById(R.id.demote); - demote.setOnClickListener(mOnDemoteClick); + final View settingsButton = findViewById(R.id.info); + settingsButton.setOnClickListener(getSettingsOnClickListener()); + settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); + + updateToggleActions(); } private void bindHeader() { @@ -315,26 +293,6 @@ public class NotificationConversationInfo extends LinearLayout implements // Delegate bindDelegate(); - - // Set up app settings link (i.e. Customize) - View settingsLinkView = findViewById(R.id.app_settings); - Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, - mNotificationChannel, - mSbn.getId(), mSbn.getTag()); - if (settingsIntent != null - && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { - settingsLinkView.setVisibility(VISIBLE); - settingsLinkView.setOnClickListener((View view) -> { - mAppSettingsClickListener.onClick(view, settingsIntent); - }); - } else { - settingsLinkView.setVisibility(View.GONE); - } - - // System Settings button. - final View settingsButton = findViewById(R.id.info); - settingsButton.setOnClickListener(getSettingsOnClickListener()); - settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); } private OnClickListener getSettingsOnClickListener() { @@ -424,15 +382,12 @@ public class NotificationConversationInfo extends LinearLayout implements private void bindDelegate() { TextView delegateView = findViewById(R.id.delegate_name); - TextView dividerView = findViewById(R.id.pkg_divider); if (!TextUtils.equals(mPackageName, mDelegatePkg)) { // this notification was posted by a delegate! delegateView.setVisibility(View.VISIBLE); - dividerView.setVisibility(View.VISIBLE); } else { delegateView.setVisibility(View.GONE); - dividerView.setVisibility(View.GONE); } } @@ -492,26 +447,37 @@ public class NotificationConversationInfo extends LinearLayout implements } } - private Intent getAppSettingsIntent(PackageManager pm, String packageName, - NotificationChannel channel, int id, String tag) { - Intent intent = new Intent(Intent.ACTION_MAIN) - .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) - .setPackage(packageName); - final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ); - if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { - return null; + private void updateToggleActions() { + ImageButton favorite = findViewById(R.id.fave); + if (mNotificationChannel.isImportantConversation()) { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_favorite)); + favorite.setImageResource(R.drawable.ic_important); + } else { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_unfavorite)); + favorite.setImageResource(R.drawable.ic_important_outline); } - final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; - intent.setClassName(activityInfo.packageName, activityInfo.name); - if (channel != null) { - intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId()); + + ImageButton mute = findViewById(R.id.mute); + if (mNotificationChannel.getImportance() >= IMPORTANCE_DEFAULT + || mNotificationChannel.getImportance() == IMPORTANCE_UNSPECIFIED) { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_unmute)); + mute.setImageResource(R.drawable.ic_notifications_alert); + } else { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_mute)); + mute.setImageResource(R.drawable.ic_notifications_silence); } - intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id); - intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag); - return intent; + } + + private void updateChannel() { + Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); + bgHandler.post( + new UpdateChannelRunnable(mINotificationManager, mPackageName, + mAppUid, mSelectedAction, mNotificationChannel)); + mVisualStabilityManager.temporarilyAllowReordering(); } /** @@ -556,11 +522,7 @@ public class NotificationConversationInfo extends LinearLayout implements @Override public boolean handleCloseControls(boolean save, boolean force) { if (save && mSelectedAction > -1) { - Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - bgHandler.post( - new UpdateChannelRunnable(mINotificationManager, mPackageName, - mAppUid, mSelectedAction, mNotificationChannel)); - mVisualStabilityManager.temporarilyAllowReordering(); + updateChannel(); } return false; } @@ -575,19 +537,7 @@ public class NotificationConversationInfo extends LinearLayout implements return false; } - static class UpdateChannelRunnable implements Runnable { - - @Retention(SOURCE) - @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE, - ACTION_DEMOTE}) - private @interface Action {} - static final int ACTION_BUBBLE = 0; - static final int ACTION_HOME = 1; - static final int ACTION_FAVORITE = 2; - static final int ACTION_SNOOZE = 3; - static final int ACTION_MUTE = 4; - static final int ACTION_DEMOTE = 5; - static final int ACTION_UNBUBBLE = 6; + class UpdateChannelRunnable implements Runnable { private final INotificationManager mINotificationManager; private final String mAppPkg; @@ -621,8 +571,8 @@ public class NotificationConversationInfo extends LinearLayout implements } break; case ACTION_FAVORITE: - // TODO: extend beyond DND - mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd()); + mChannelToUpdate.setImportantConversation( + !mChannelToUpdate.isImportantConversation()); break; case ACTION_MUTE: if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED @@ -633,10 +583,6 @@ public class NotificationConversationInfo extends LinearLayout implements mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT)); } break; - case ACTION_DEMOTE: - mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted()); - break; - } if (channelSettingChanged) { @@ -646,13 +592,7 @@ public class NotificationConversationInfo extends LinearLayout implements } catch (RemoteException e) { Log.e(TAG, "Unable to update notification channel", e); } + ThreadUtils.postOnMainThread(() -> updateToggleActions()); } } - - @Retention(SOURCE) - @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE}) - private @interface AlertingBehavior {} - private static final int BEHAVIOR_ALERTING = 0; - private static final int BEHAVIOR_SILENT = 1; - private static final int BEHAVIOR_BUBBLE = 2; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6789c814dcee..352abcfc9214 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -384,6 +385,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx guts.resetFalsingCheck(); mOnSettingsClickListener.onSettingsClick(sbn.getKey()); startAppNotificationSettingsActivity(packageName, appUid, channel, row); + notificationInfoView.closeControls(v, false); }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index 9b95bff4921c..9bd8d4782672 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -101,7 +101,7 @@ public interface NotificationRowContentBinder { */ int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3; - int FLAG_CONTENT_VIEW_ALL = ~0; + int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1; /** * Parameters for content view binding @@ -146,9 +146,7 @@ public interface NotificationRowContentBinder { * Callback for after the content views finish inflating. * * @param entry the entry with the content views set - * @param inflatedFlags the flags associated with the content views that were inflated */ - void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags); + void onAsyncInflationFinished(NotificationEntry entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java new file mode 100644 index 000000000000..5170d0b85b17 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +/** + * Parameters for {@link RowContentBindStage}. + */ +public final class RowContentBindParams { + private boolean mUseLowPriority; + private boolean mUseChildInGroup; + private boolean mUseIncreasedHeight; + private boolean mUseIncreasedHeadsUpHeight; + private boolean mViewsNeedReinflation; + private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS; + + /** + * Content views that are out of date and need to be rebound. + * + * TODO: This should go away once {@link NotificationContentInflater} is broken down into + * smaller stages as then the stage itself would be invalidated. + */ + private @InflationFlag int mDirtyContentViews = mContentViews; + + /** + * Set whether content should use a low priority version of its content views. + */ + public void setUseLowPriority(boolean useLowPriority) { + if (mUseLowPriority != useLowPriority) { + mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED); + } + mUseLowPriority = useLowPriority; + } + + public boolean useLowPriority() { + return mUseLowPriority; + } + + /** + * Set whether content should use group child version of its content views. + */ + public void setUseChildInGroup(boolean useChildInGroup) { + if (mUseChildInGroup != useChildInGroup) { + mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED); + } + mUseChildInGroup = useChildInGroup; + } + + public boolean useChildInGroup() { + return mUseChildInGroup; + } + + /** + * Set whether content should use an increased height version of its contracted view. + */ + public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) { + if (mUseIncreasedHeight != useIncreasedHeight) { + mDirtyContentViews |= FLAG_CONTENT_VIEW_CONTRACTED; + } + mUseIncreasedHeight = useIncreasedHeight; + } + + public boolean useIncreasedHeight() { + return mUseIncreasedHeight; + } + + /** + * Set whether content should use an increased height version of its heads up view. + */ + public void setUseIncreasedHeadsUpHeight(boolean useIncreasedHeadsUpHeight) { + if (mUseIncreasedHeadsUpHeight != useIncreasedHeadsUpHeight) { + mDirtyContentViews |= FLAG_CONTENT_VIEW_HEADS_UP; + } + mUseIncreasedHeadsUpHeight = useIncreasedHeadsUpHeight; + } + + public boolean useIncreasedHeadsUpHeight() { + return mUseIncreasedHeadsUpHeight; + } + + /** + * Require the specified content views to be bound after the rebind request. + * + * @see InflationFlag + */ + public void requireContentViews(@InflationFlag int contentViews) { + @InflationFlag int newContentViews = contentViews &= ~mContentViews; + mContentViews |= contentViews; + mDirtyContentViews |= newContentViews; + } + + /** + * Free the content view so that it will no longer be bound after the rebind request. + * + * @see InflationFlag + */ + public void freeContentViews(@InflationFlag int contentViews) { + mContentViews &= ~contentViews; + mDirtyContentViews &= ~contentViews; + } + + public @InflationFlag int getContentViews() { + return mContentViews; + } + + /** + * Request that all content views be rebound. This may happen if, for example, the underlying + * layout has changed. + */ + public void rebindAllContentViews() { + mDirtyContentViews = mContentViews; + } + + /** + * Clears all dirty content views so that they no longer need to be rebound. + */ + void clearDirtyContentViews() { + mDirtyContentViews = 0; + } + + public @InflationFlag int getDirtyContentViews() { + return mDirtyContentViews; + } + + /** + * Set whether all content views need to be reinflated even if cached. + * + * TODO: This should probably be a more global config on {@link NotifBindPipeline} since this + * generally corresponds to a Context/Configuration change that all stages should know about. + */ + public void setNeedsReinflation(boolean needsReinflation) { + mViewsNeedReinflation = needsReinflation; + @InflationFlag int currentContentViews = mContentViews; + mDirtyContentViews |= currentContentViews; + } + + public boolean needsReinflation() { + return mViewsNeedReinflation; + } + + /** + * Content views that should be inflated by default for all notifications. + */ + @InflationFlag private static final int DEFAULT_INFLATION_FLAGS = + FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java new file mode 100644 index 000000000000..f1241799d3d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; + +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; + +import androidx.annotation.NonNull; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * A stage that binds all content views for an already inflated {@link ExpandableNotificationRow}. + * + * In the farther future, the binder logic and consequently this stage should be broken into + * smaller stages. + */ +@Singleton +public class RowContentBindStage extends BindStage<RowContentBindParams> { + private final NotificationRowContentBinder mBinder; + private final IStatusBarService mStatusBarService; + + @Inject + RowContentBindStage( + NotificationRowContentBinder binder, + IStatusBarService statusBarService) { + mBinder = binder; + mStatusBarService = statusBarService; + } + + @Override + protected void executeStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, + @NonNull StageCallback callback) { + RowContentBindParams params = getStageParams(entry); + + // Resolve content to bind/unbind. + @InflationFlag int inflationFlags = params.getContentViews(); + @InflationFlag int invalidatedFlags = params.getDirtyContentViews(); + + @InflationFlag int contentToBind = invalidatedFlags & inflationFlags; + @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL; + + // Bind/unbind with parameters + mBinder.unbindContent(entry, row, contentToUnbind); + + BindParams bindParams = new BindParams(); + bindParams.isLowPriority = params.useLowPriority(); + bindParams.isChildInGroup = params.useChildInGroup(); + bindParams.usesIncreasedHeight = params.useIncreasedHeight(); + bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight(); + boolean forceInflate = params.needsReinflation(); + + InflationCallback inflationCallback = new InflationCallback() { + @Override + public void handleInflationException(NotificationEntry entry, Exception e) { + entry.setHasInflationError(true); + try { + final StatusBarNotification sbn = entry.getSbn(); + mStatusBarService.onNotificationError( + sbn.getPackageName(), + sbn.getTag(), + sbn.getId(), + sbn.getUid(), + sbn.getInitialPid(), + e.getMessage(), + sbn.getUserId()); + } catch (RemoteException ex) { + } + } + + @Override + public void onAsyncInflationFinished(NotificationEntry entry) { + entry.setHasInflationError(false); + getStageParams(entry).clearDirtyContentViews(); + callback.onStageFinished(entry); + } + }; + mBinder.cancelBind(entry, row); + mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback); + } + + @Override + protected void abortStage( + @NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + mBinder.cancelBind(entry, row); + } + + @Override + protected RowContentBindParams newStageParams() { + return new RowContentBindParams(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index c173b4dbaebe..6feffe654630 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -26,7 +26,6 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import javax.inject.Inject; @@ -37,7 +36,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private static final String TAG = "RowInflaterTask"; private static final boolean TRACE_ORIGIN = true; - private final NotificationRowComponent.Builder mNotificationRowComponentBuilder; private RowInflationFinishedListener mListener; private NotificationEntry mEntry; @@ -45,10 +43,7 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private Throwable mInflateOrigin; @Inject - public RowInflaterTask( - NotificationRowComponent.Builder notificationRowComponentBuilder) { - super(); - mNotificationRowComponentBuilder = notificationRowComponentBuilder; + public RowInflaterTask() { } /** @@ -75,12 +70,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf public void onInflateFinished(View view, int resid, ViewGroup parent) { if (!mCancelled) { try { - // Setup the controller for the view. - NotificationRowComponent component = mNotificationRowComponentBuilder - .activatableNotificationView((ActivatableNotificationView) view) - .build(); - component.getActivatableNotificationViewController().init(); - mEntry.onInflationTaskFinished(); mListener.onInflationFinished((ExpandableNotificationRow) view); } catch (Throwable t) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java new file mode 100644 index 000000000000..a3dfa608c709 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableOutlineView; +import com.android.systemui.statusbar.notification.row.ExpandableView; + +import dagger.Binds; +import dagger.Module; + +/** + * Module for NotificationRowComponent. + */ +@Module +public interface ActivatableNotificationViewModule { + /** ExpandableView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableView bindExpandableView(ActivatableNotificationView view); + /** ExpandableOutlineView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableOutlineView bindExpandableOutlineView(ActivatableNotificationView view); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java new file mode 100644 index 000000000000..1dbca0cd5527 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface AppName { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java new file mode 100644 index 000000000000..433114224289 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DismissRunnable { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java new file mode 100644 index 000000000000..6d6d3e446f53 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; +import com.android.systemui.statusbar.phone.StatusBar; + +import dagger.Binds; +import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * Dagger Component for a {@link ExpandableNotificationRow}. + */ +@Subcomponent(modules = {ExpandableNotificationRowComponent.ExpandableNotificationRowModule.class, + ActivatableNotificationViewModule.class}) +@NotificationRowScope +public interface ExpandableNotificationRowComponent { + + /** + * Builder for {@link NotificationRowComponent}. + */ + @Subcomponent.Builder + interface Builder { + // TODO: NotificationEntry contains a reference to ExpandableNotificationRow, so it + // should be possible to pull one from the other, but they aren't connected at the time + // this component is constructed. + @BindsInstance + Builder expandableNotificationRow(ExpandableNotificationRow view); + @BindsInstance + Builder notificationEntry(NotificationEntry entry); + @BindsInstance + Builder onDismissRunnable(@DismissRunnable Runnable runnable); + @BindsInstance + Builder rowContentBindStage(RowContentBindStage rowContentBindStage); + @BindsInstance + Builder inflationCallback(NotificationRowContentBinder.InflationCallback inflationCallback); + @BindsInstance + Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter); + ExpandableNotificationRowComponent build(); + } + + /** + * Creates a ExpandableNotificationRowController. + */ + @NotificationRowScope + ExpandableNotificationRowController getExpandableNotificationRowController(); + + /** + * Dagger Module that extracts interesting properties from an ExpandableNotificationRow. + */ + @Module + abstract class ExpandableNotificationRowModule { + + /** ExpandableNotificationRow is provided as an instance of ActivatableNotificationView. */ + @Binds + abstract ActivatableNotificationView bindExpandableView(ExpandableNotificationRow view); + + @Provides + static StatusBarNotification provideStatusBarNotification( + NotificationEntry notificationEntry) { + return notificationEntry.getSbn(); + } + + @Provides + @NotificationKey + static String provideNotificationKey(StatusBarNotification statusBarNotification) { + return statusBarNotification.getKey(); + } + + @Provides + @AppName + static String provideAppName(Context context, StatusBarNotification statusBarNotification) { + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + PackageManager pmUser = StatusBar.getPackageManagerForUser( + context, statusBarNotification.getUser().getIdentifier()); + final String pkg = statusBarNotification.getPackageName(); + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + return String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + + return pkg; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java new file mode 100644 index 000000000000..b1fff383cd5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationKey { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java index f16ea7ae23e9..1f535c5e3f56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java @@ -16,24 +16,17 @@ package com.android.systemui.statusbar.notification.row.dagger; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Scope; - import dagger.BindsInstance; import dagger.Subcomponent; /** * Dagger subcomponent for Notification related views. */ -@Subcomponent(modules = {}) -@NotificationRowComponent.NotificationRowScope +@Subcomponent(modules = {ActivatableNotificationViewModule.class}) +@NotificationRowScope public interface NotificationRowComponent { /** * Builder for {@link NotificationRowComponent}. @@ -46,14 +39,6 @@ public interface NotificationRowComponent { } /** - * Scope annotation for singleton items within the StatusBarComponent. - */ - @Documented - @Retention(RUNTIME) - @Scope - @interface NotificationRowScope {} - - /** * Creates a ActivatableNotificationViewController. */ @NotificationRowScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java new file mode 100644 index 000000000000..4555b839a3f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +/** + * Scope annotation for singleton items within the StatusBarComponent. + */ +@Documented +@Retention(RUNTIME) +@Scope +public @interface NotificationRowScope {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt new file mode 100644 index 000000000000..5757fe8040f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack + +import android.content.Context +import android.service.notification.NotificationListenerService.REASON_APP_CANCEL +import android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL +import android.service.notification.NotificationListenerService.REASON_CANCEL +import android.service.notification.NotificationListenerService.REASON_CANCEL_ALL +import android.service.notification.NotificationListenerService.REASON_CLICK +import android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout + +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.R +import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController +import com.android.systemui.statusbar.notification.NotificationEntryListener +import com.android.systemui.statusbar.notification.NotificationEntryManager +import com.android.systemui.statusbar.notification.row.DungeonRow +import com.android.systemui.util.Assert + +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Controller for the bottom area of NotificationStackScrollLayout. It owns swiped-away foreground + * service notifications and can reinstantiate them when requested. + */ +@Singleton +class ForegroundServiceSectionController @Inject constructor( + val entryManager: NotificationEntryManager, + val featureController: ForegroundServiceDismissalFeatureController +) { + private val TAG = "FgsSectionController" + private var context: Context? = null + + private val entries = mutableSetOf<NotificationEntry>() + + private var entriesView: View? = null + + init { + if (featureController.isForegroundServiceDismissalEnabled()) { + entryManager.addNotificationRemoveInterceptor(this::shouldInterceptRemoval) + + entryManager.addNotificationEntryListener(object : NotificationEntryListener { + override fun onPostEntryUpdated(entry: NotificationEntry) { + if (entries.contains(entry)) { + removeEntry(entry) + addEntry(entry) + update() + } + } + }) + } + } + + private fun shouldInterceptRemoval( + key: String, + entry: NotificationEntry?, + reason: Int + ): Boolean { + Assert.isMainThread() + val isClearAll = reason == REASON_CANCEL_ALL + val isUserDismiss = reason == REASON_CANCEL || reason == REASON_CLICK + val isAppCancel = reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL + val isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED + + if (entry == null) return false + + // We only want to retain notifications that the user dismissed + // TODO: centralize the entry.isClearable logic and this so that it's clear when a notif is + // clearable + if (isUserDismiss && !entry.sbn.isClearable) { + if (!hasEntry(entry)) { + addEntry(entry) + update() + } + // TODO: This isn't ideal. Slightly better would at least be to have NEM update the + // notif list when an entry gets intercepted + entryManager.updateNotifications( + "FgsSectionController.onNotificationRemoveRequested") + return true + } else if ((isClearAll || isSummaryCancel) && !entry.sbn.isClearable) { + // In the case where a FGS notification is part of a group that is cleared or a clear + // all, we actually want to stop its removal but also not put it into the dungeon + return true + } else if (hasEntry(entry)) { + removeEntry(entry) + update() + return false + } + + return false + } + + private fun removeEntry(entry: NotificationEntry) { + Assert.isMainThread() + entries.remove(entry) + } + + private fun addEntry(entry: NotificationEntry) { + Assert.isMainThread() + entries.add(entry) + } + + fun hasEntry(entry: NotificationEntry): Boolean { + Assert.isMainThread() + return entries.contains(entry) + } + + fun initialize(context: Context) { + this.context = context + } + + fun createView(li: LayoutInflater): View { + entriesView = li.inflate(R.layout.foreground_service_dungeon, null) + // Start out gone + entriesView!!.visibility = View.GONE + return entriesView!! + } + + private fun update() { + Assert.isMainThread() + if (entriesView == null) { + throw IllegalStateException("ForegroundServiceSectionController is trying to show " + + "dismissed fgs notifications without having been initialized!") + } + + // TODO: these views should be recycled and not inflating on the main thread + (entriesView!!.findViewById(R.id.entry_list) as LinearLayout).apply { + removeAllViews() + entries.sortedBy { it.ranking.rank }.forEach { entry -> + val child = LayoutInflater.from(context) + .inflate(R.layout.foreground_service_dungeon_row, null) as DungeonRow + + child.entry = entry + child.setOnClickListener { + removeEntry(child.entry!!) + update() + entry.row.unDismiss() + entry.row.resetTranslation() + entryManager.updateNotifications("ForegroundServiceSectionController.onClick") + } + + addView(child) + } + } + + if (entries.isEmpty()) { + entriesView?.visibility = View.GONE + } else { + entriesView?.visibility = View.VISIBLE + } + } +} 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 11ead8bd2576..4b9976cc2097 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 @@ -99,6 +99,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper.DragDownCallback; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; @@ -106,6 +107,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.FakeShadowView; +import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -118,6 +120,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.FooterView; +import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -328,6 +331,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd return true; } }; + private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() { + @Override + public void onUserChanged(int userId) { + updateSensitiveness(false /* animated */); + } + }; private StatusBar mStatusBar; private int[] mTempInt2 = new int[2]; private boolean mGenerateChildOrderChangedEvent; @@ -497,6 +506,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private final NotificationGutsManager mNotificationGutsManager; private final NotificationSectionsManager mSectionsManager; + private final ForegroundServiceSectionController mFgsSectionController; + private ForegroundServiceDungeonView mFgsSectionView; private boolean mAnimateBottomOnLayout; private float mLastSentAppear; private float mLastSentExpandedHeight; @@ -516,7 +527,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGutsManager notificationGutsManager, ZenModeController zenController, - NotificationSectionsManager notificationSectionsManager) { + NotificationSectionsManager notificationSectionsManager, + ForegroundServiceSectionController fgsSectionController, + ForegroundServiceDismissalFeatureController fgsFeatureController + ) { super(context, attrs, 0, 0); Resources res = getResources(); @@ -532,6 +546,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mKeyguardBypassController = keyguardBypassController; mFalsingManager = falsingManager; mZenController = zenController; + mFgsSectionController = fgsSectionController; mSectionsManager = notificationSectionsManager; mSectionsManager.initialize(this, LayoutInflater.from(context)); @@ -561,6 +576,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated); mRoundnessManager.setOnRoundingChangedCallback(this::invalidate); addOnExpandedHeightChangedListener(mRoundnessManager::setExpanded); + mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); setOutlineProvider(mOutlineProvider); // Blocking helper manager wants to know the expanded state, update as well. @@ -604,6 +620,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd dynamicPrivacyController.addListener(this); mDynamicPrivacyController = dynamicPrivacyController; mStatusbarStateController = statusBarStateController; + initializeForegroundServiceSection(fgsFeatureController); + } + + private void initializeForegroundServiceSection( + ForegroundServiceDismissalFeatureController featureController) { + if (featureController.isForegroundServiceDismissalEnabled()) { + LayoutInflater li = LayoutInflater.from(mContext); + mFgsSectionView = + (ForegroundServiceDungeonView) mFgsSectionController.createView(li); + addView(mFgsSectionView, -1); + } } private void updateDismissRtlSetting(boolean dismissRtl) { @@ -3364,7 +3391,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (currentIndex == -1) { boolean isTransient = false; if (child instanceof ExpandableNotificationRow - && ((ExpandableNotificationRow) child).getTransientContainer() != null) { + && child.getTransientContainer() != null) { isTransient = true; } Log.e(TAG, "Attempting to re-position " @@ -3377,10 +3404,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (child != null && child.getParent() == this && currentIndex != newIndex) { mChangePositionInProgress = true; - ((ExpandableView) child).setChangingPosition(true); + child.setChangingPosition(true); removeView(child); addView(child, newIndex); - ((ExpandableView) child).setChangingPosition(false); + child.setChangingPosition(false); mChangePositionInProgress = false; if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { mChildrenChangingPositions.add(child); @@ -4611,7 +4638,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void setHideSensitive(boolean hideSensitive, boolean animate) { + private void updateSensitiveness(boolean animate) { + boolean hideSensitive = mLockscreenUserManager.isAnyProfilePublicMode(); if (hideSensitive != mAmbientState.isHideSensitive()) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -5306,7 +5334,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private void onStatePostChange() { boolean onKeyguard = onKeyguard(); - boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode(); if (mHeadsUpAppearanceController != null) { mHeadsUpAppearanceController.onStateChanged(); @@ -5314,7 +5341,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd SysuiStatusBarStateController state = (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class); - setHideSensitive(publicMode, state.goingToFullShade() /* animate */); + updateSensitiveness(state.goingToFullShade() /* animate */); setDimmed(onKeyguard, state.fromShadeLocked() /* animate */); setExpandingEnabled(!onKeyguard); ActivatableNotificationView activatedChild = getActivatedChild(); @@ -5627,15 +5654,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd */ @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void onUpdateRowStates() { - changeViewPosition(mFooterView, -1); // The following views will be moved to the end of mStackScroller. This counter represents // the offset from the last child. Initialized to 1 for the very last position. It is post- // incremented in the following "changeViewPosition" calls so that its value is correct for // subsequent calls. int offsetFromEnd = 1; - changeViewPosition(mEmptyShadeView, - getChildCount() - offsetFromEnd++); + if (mFgsSectionView != null) { + changeViewPosition(mFgsSectionView, getChildCount() - offsetFromEnd++); + } + changeViewPosition(mFooterView, getChildCount() - offsetFromEnd++); + changeViewPosition(mEmptyShadeView, getChildCount() - offsetFromEnd++); // No post-increment for this call because it is the last one. Make sure to add one if // another "changeViewPosition" call is ever added. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index db692c8a8c89..44a320419309 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -422,9 +422,7 @@ public class EdgeBackGestureHandler implements DisplayListener, } private void updateDisplaySize() { - mContext.getSystemService(DisplayManager.class) - .getDisplay(mDisplayId) - .getRealSize(mDisplaySize); + mContext.getDisplay().getRealSize(mDisplaySize); if (mEdgeBackPlugin != null) { mEdgeBackPlugin.setDisplaySize(mDisplaySize); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt deleted file mode 100644 index 53601babfd56..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt +++ /dev/null @@ -1,102 +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.phone - -import android.content.Context -import android.view.View -import android.view.ViewGroup.MarginLayoutParams -import android.widget.FrameLayout -import com.android.systemui.plugins.NPVPlugin -import com.android.systemui.plugins.PluginListener -import com.android.systemui.qs.TouchAnimator -import com.android.systemui.shared.plugins.PluginManager - -/** - * Manages the NPVPlugin view and state - * - * Abstracts NPVPlugin from NPV and helps animate on expansion and respond to changes in Config. - */ -class NPVPluginManager( - var parent: FrameLayout, - val pluginManager: PluginManager -) : PluginListener<NPVPlugin> { - - private var plugin: NPVPlugin? = null - private var animator = createAnimator() - private var yOffset = 0f - - private fun createAnimator() = TouchAnimator.Builder() - .addFloat(parent, "alpha", 1f, 0f) - .addFloat(parent, "scaleY", 1f, 0f) - .build() - - init { - pluginManager.addPluginListener(NPVPlugin.ACTION, this, NPVPlugin::class.java, false) - parent.pivotY = 0f - } - - override fun onPluginConnected(plugin: NPVPlugin, pluginContext: Context) { - parent.removeAllViews() - plugin.attachToRoot(parent) - this.plugin = plugin - parent.visibility = View.VISIBLE - } - - fun changeVisibility(visibility: Int) { - parent.visibility = if (plugin != null) visibility else View.GONE - } - - fun destroy() { - plugin?.onDestroy() - pluginManager.removePluginListener(this) - } - - override fun onPluginDisconnected(plugin: NPVPlugin) { - if (this.plugin == plugin) { - this.plugin = null - parent.removeAllViews() - parent.visibility = View.GONE - } - } - - fun setListening(listening: Boolean) { - plugin?.setListening(listening) - } - - fun setExpansion(expansion: Float, headerTranslation: Float, heightDiff: Float) { - parent.setTranslationY(expansion * heightDiff + headerTranslation + yOffset) - if (!expansion.isNaN()) animator.setPosition(expansion) - } - - fun replaceFrameLayout(newParent: FrameLayout) { - newParent.visibility = parent.visibility - parent.removeAllViews() - plugin?.attachToRoot(newParent) - parent = newParent - animator = createAnimator() - } - - fun getHeight() = - if (plugin != null) { - parent.height + (parent.getLayoutParams() as MarginLayoutParams).topMargin - } else 0 - - fun setYOffset(y: Float) { - yOffset = y - parent.setTranslationY(yOffset) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 896b6e570da2..bdca9a452484 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -28,11 +28,12 @@ import com.android.systemui.Dependency; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.AlertingNotificationManager; -import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -67,6 +68,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>(); private HeadsUpManager mHeadsUpManager; + private final RowContentBindStage mRowContentBindStage; private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); @@ -75,8 +77,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private boolean mIsDozing; @Inject - public NotificationGroupAlertTransferHelper() { + public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) { Dependency.get(StatusBarStateController.class).addCallback(this); + mRowContentBindStage = bindStage; } /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */ @@ -190,21 +193,6 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } } - // Called when the entry's reinflation has finished. If there is an alert pending, we - // then show the alert. - @Override - public void onEntryReinflated(NotificationEntry entry) { - PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey()); - if (alertInfo != null) { - if (alertInfo.isStillValid()) { - alertNotificationWhenPossible(entry, mHeadsUpManager); - } else { - // The transfer is no longer valid. Free the content. - entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); - } - } - } - @Override public void onEntryRemoved( @Nullable NotificationEntry entry, @@ -392,10 +380,21 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private void alertNotificationWhenPossible(@NonNull NotificationEntry entry, @NonNull AlertingNotificationManager alertManager) { @InflationFlag int contentFlag = alertManager.getContentFlag(); - if (!entry.getRow().isInflationFlagSet(contentFlag)) { + final RowContentBindParams params = mRowContentBindStage.getStageParams(entry); + if ((params.getContentViews() & contentFlag) == 0) { mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry)); - entry.getRow().setInflationFlags(contentFlag); - entry.getRow().inflateViews(); + params.requireContentViews(contentFlag); + mRowContentBindStage.requestRebind(entry, en -> { + PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey()); + if (alertInfo != null) { + if (alertInfo.isStillValid()) { + alertNotificationWhenPossible(entry, mHeadsUpManager); + } else { + // The transfer is no longer valid. Free the content. + entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); + } + } + }); return; } if (alertManager.isAlerting(entry.getKey())) { @@ -426,9 +425,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis /** * The notification is still pending inflation but we've decided that we no longer need * the content view (e.g. suppression might have changed and we decided we need to transfer - * back). However, there is no way to abort just this inflation if other inflation requests - * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead - * we just flag it as aborted and free when it's inflated. + * back). + * + * TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}. */ boolean mAbortOnInflation; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 6112ae88f634..d2186f959aba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -25,10 +25,8 @@ import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.Fragment; import android.app.StatusBarManager; -import android.content.Context; import android.content.pm.ResolveInfo; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -40,8 +38,6 @@ import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricSourceType; import android.os.PowerManager; import android.os.SystemClock; -import android.provider.DeviceConfig; -import android.provider.Settings; import android.util.Log; import android.util.MathUtils; import android.view.LayoutInflater; @@ -57,7 +53,6 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; @@ -73,13 +68,10 @@ import com.android.systemui.doze.DozeLog; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.HomeControlsPlugin; -import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.qs.QSFragment; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.GestureRecorder; @@ -113,7 +105,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.InjectionInflationController; -import com.android.systemui.util.Utils; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -173,8 +164,6 @@ public class NotificationPanelViewController extends PanelViewController { private final ConfigurationController mConfigurationController; private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder; - private double mQqsSplitFraction; - // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is // changed. private static final int CAP_HEIGHT = 1456; @@ -258,7 +247,6 @@ public class NotificationPanelViewController extends PanelViewController { private View mQsNavbarScrim; private NotificationsQuickSettingsContainer mNotificationContainerParent; private NotificationStackScrollLayout mNotificationStackScroller; - private FrameLayout mHomeControlsLayout; private boolean mAnimateNextPositionUpdate; private int mTrackingPointer; @@ -446,9 +434,6 @@ public class NotificationPanelViewController extends PanelViewController { */ private boolean mDelayShowingKeyguardStatusBar; - private PluginManager mPluginManager; - private FrameLayout mPluginFrame; - private NPVPluginManager mNPVPluginManager; private int mOldLayoutDirection; @Inject @@ -457,7 +442,7 @@ public class NotificationPanelViewController extends PanelViewController { NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler, DynamicPrivacyController dynamicPrivacyController, KeyguardBypassController bypassController, FalsingManager falsingManager, - PluginManager pluginManager, ShadeController shadeController, + ShadeController shadeController, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationEntryManager notificationEntryManager, KeyguardStateController keyguardStateController, @@ -523,7 +508,6 @@ public class NotificationPanelViewController extends PanelViewController { }); mBottomAreaShadeAlphaAnimator.setDuration(160); mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT); - mPluginManager = pluginManager; mShadeController = shadeController; mLockscreenUserManager = notificationLockscreenUserManager; mEntryManager = notificationEntryManager; @@ -553,7 +537,6 @@ public class NotificationPanelViewController extends PanelViewController { mBigClockContainer = mView.findViewById(R.id.big_clock_container); keyguardClockSwitch.setBigClockContainer(mBigClockContainer); - mHomeControlsLayout = mView.findViewById(R.id.home_controls_layout); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); mNotificationStackScroller = mView.findViewById(R.id.notification_stack_scroller); mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener); @@ -563,12 +546,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area); mQsNavbarScrim = mView.findViewById(R.id.qs_navbar_scrim); mLastOrientation = mResources.getConfiguration().orientation; - mPluginFrame = mView.findViewById(R.id.plugin_frame); - if (Settings.System.getInt(mView.getContext().getContentResolver(), "npv_plugin_flag", 0) - == 1) { - mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager); - } - initBottomArea(); @@ -592,19 +569,6 @@ public class NotificationPanelViewController extends PanelViewController { } }); - mPluginManager.addPluginListener(new PluginListener<HomeControlsPlugin>() { - - @Override - public void onPluginConnected(HomeControlsPlugin plugin, Context pluginContext) { - plugin.sendParentGroup(mHomeControlsLayout); - } - - @Override - public void onPluginDisconnected(HomeControlsPlugin plugin) { - - } - }, HomeControlsPlugin.class, false); - mView.setRtlChangeListener(layoutDirection -> { if (layoutDirection != mOldLayoutDirection) { mAffordanceHelper.onRtlPropertiesChanged(); @@ -637,9 +601,6 @@ public class NotificationPanelViewController extends PanelViewController { com.android.internal.R.dimen.status_bar_height); mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); - mQqsSplitFraction = ((float) mResources.getInteger(R.integer.qqs_split_fraction)) / ( - mResources.getInteger(R.integer.qqs_split_fraction) + mResources.getInteger( - R.integer.qs_split_fraction)); } /** @@ -679,18 +640,6 @@ public class NotificationPanelViewController extends PanelViewController { lp.gravity = panelGravity; mNotificationStackScroller.setLayoutParams(lp); } - int sideMargin = mResources.getDimensionPixelOffset(R.dimen.notification_side_paddings); - int topMargin = sideMargin; - lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams(); - if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin - || lp.rightMargin != sideMargin || lp.topMargin != topMargin) { - lp.width = qsWidth; - lp.gravity = panelGravity; - lp.leftMargin = sideMargin; - lp.rightMargin = sideMargin; - lp.topMargin = topMargin; - mPluginFrame.setLayoutParams(lp); - } } private void reInflateViews() { @@ -732,41 +681,6 @@ public class NotificationPanelViewController extends PanelViewController { if (mOnReinflationListener != null) { mOnReinflationListener.run(); } - reinflatePluginContainer(); - } - - private void reinflatePluginContainer() { - int index = mView.indexOfChild(mPluginFrame); - mView.removeView(mPluginFrame); - mPluginFrame = (FrameLayout) mInjectionInflationController.injectable( - LayoutInflater.from(mView.getContext())).inflate( - R.layout.status_bar_expanded_plugin_frame, mView, false); - mView.addView(mPluginFrame, index); - - Resources res = mView.getResources(); - int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width); - int panelGravity = mView.getResources().getInteger( - R.integer.notification_panel_layout_gravity); - FrameLayout.LayoutParams lp; - int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings); - int topMargin = res.getDimensionPixelOffset( - com.android.internal.R.dimen.quick_qs_total_height); - if (Utils.useQsMediaPlayer(mView.getContext())) { - topMargin = res.getDimensionPixelOffset( - com.android.internal.R.dimen.quick_qs_total_height_with_media); - } - lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams(); - if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin - || lp.rightMargin != sideMargin || lp.topMargin != topMargin) { - lp.width = qsWidth; - lp.gravity = panelGravity; - lp.leftMargin = sideMargin; - lp.rightMargin = sideMargin; - lp.topMargin = topMargin; - mPluginFrame.setLayoutParams(lp); - } - - if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame); } private void initBottomArea() { @@ -1266,17 +1180,6 @@ public class NotificationPanelViewController extends PanelViewController { // earlier so the state is already up to date when dragging down. setListening(true); } - if (isQsSplitEnabled() && !mKeyguardShowing) { - if (mQsExpandImmediate) { - mNotificationStackScroller.setVisibility(View.GONE); - mQsFrame.setVisibility(View.VISIBLE); - mHomeControlsLayout.setVisibility(View.VISIBLE); - } else { - mNotificationStackScroller.setVisibility(View.VISIBLE); - mQsFrame.setVisibility(View.GONE); - mHomeControlsLayout.setVisibility(View.GONE); - } - } return false; } @@ -1286,17 +1189,6 @@ public class NotificationPanelViewController extends PanelViewController { || y <= mQs.getView().getY() + mQs.getView().getHeight()); } - private boolean isOnQsEndArea(float x) { - if (!isQsSplitEnabled()) return false; - if (mView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) { - return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth() - && x <= mQsFrame.getX() + mQsFrame.getWidth(); - } else { - return x >= mQsFrame.getX() - && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth(); - } - } - private boolean isOpenQsEvent(MotionEvent event) { final int pointerCount = event.getPointerCount(); final int action = event.getActionMasked(); @@ -1317,9 +1209,7 @@ public class NotificationPanelViewController extends PanelViewController { MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed( MotionEvent.BUTTON_TERTIARY)); - final boolean onHeaderRight = isOnQsEndArea(event.getX()); - - return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight; + return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; } private void handleQsDown(MotionEvent event) { @@ -1659,10 +1549,7 @@ public class NotificationPanelViewController extends PanelViewController { mBarState != StatusBarState.KEYGUARD && (!mQsExpanded || mQsExpansionFromOverscroll)); updateEmptyShadeView(); - if (mNPVPluginManager != null) { - mNPVPluginManager.changeVisibility( - (mBarState != StatusBarState.KEYGUARD) ? View.VISIBLE : View.INVISIBLE); - } + mQsNavbarScrim.setVisibility( mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling && mQsScrimEnabled ? View.VISIBLE : View.INVISIBLE); @@ -1718,9 +1605,6 @@ public class NotificationPanelViewController extends PanelViewController { float qsExpansionFraction = getQsExpansionFraction(); mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation()); int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight(); - if (mNPVPluginManager != null) { - mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff); - } mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction); } @@ -2015,7 +1899,6 @@ public class NotificationPanelViewController extends PanelViewController { targetHeight = mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight); setQsExpansion(targetHeight); - mHomeControlsLayout.setTranslationY(targetHeight); } updateExpandedHeight(expandedHeight); updateHeader(); @@ -2150,7 +2033,6 @@ public class NotificationPanelViewController extends PanelViewController { appearAmount = mNotificationStackScroller.calculateAppearFractionBypass(); } startHeight = -mQs.getQsMinExpansionHeight(); - if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight(); } float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount)) + mExpandOffset; @@ -2294,7 +2176,6 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusBar.setListening(listening); if (mQs == null) return; mQs.setListening(listening); - if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening); } @Override @@ -3029,11 +2910,6 @@ public class NotificationPanelViewController extends PanelViewController { mOnReinflationListener = onReinflationListener; } - public static boolean isQsSplitEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false); - } - public void setAlpha(float alpha) { mView.setAlpha(alpha); } @@ -3500,9 +3376,7 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - public void onUiModeChanged() { - reinflatePluginContainer(); - } + public void onUiModeChanged() {} } private class StatusBarStateListener implements StateListener { @@ -3517,11 +3391,6 @@ public class NotificationPanelViewController extends PanelViewController { mBarState = statusBarState; mKeyguardShowing = keyguardShowing; - if (mKeyguardShowing && isQsSplitEnabled()) { - mNotificationStackScroller.setVisibility(View.VISIBLE); - mQsFrame.setVisibility(View.VISIBLE); - mHomeControlsLayout.setVisibility(View.GONE); - } if (oldState == StatusBarState.KEYGUARD && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) { @@ -3544,7 +3413,6 @@ public class NotificationPanelViewController extends PanelViewController { } else { mKeyguardStatusBar.setAlpha(1f); mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); - ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing); if (keyguardShowing && oldState != mBarState) { if (mQs != null) { mQs.hideImmediately(); @@ -3622,10 +3490,6 @@ public class NotificationPanelViewController extends PanelViewController { int oldMaxHeight = mQsMaxExpansionHeight; if (mQs != null) { mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight(); - if (mNPVPluginManager != null) { - mNPVPluginManager.setYOffset(mQsMinExpansionHeight); - mQsMinExpansionHeight += mNPVPluginManager.getHeight(); - } mQsMaxExpansionHeight = mQs.getDesiredHeight(); mNotificationStackScroller.setMaxTopPadding( mQsMaxExpansionHeight + mQsNotificationTopPadding); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java index 3af80387778b..10b68b9a518d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java @@ -16,8 +16,10 @@ package com.android.systemui.statusbar.phone; +import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; @@ -180,6 +182,13 @@ public class NotificationShadeWindowController implements Callback, Dumpable, mLp.setTitle("NotificationShade"); mLp.packageName = mContext.getPackageName(); mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + + // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in + // window manager which disables the transient show behavior. + // TODO: Clean this up once that behavior moves into the Shell. + mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; + mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + mWindowManager.addView(mNotificationShadeView, mLp); mLpChanged.copyFrom(mLp); onThemeChanged(); 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 96b4b22d0580..e8bc2f58adb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java @@ -32,7 +32,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { private final PhoneStatusBarView mView; private final float mIconAlphaWhenOpaque; - private View mLeftSide, mStatusIcons, mBattery, mClock, mDivider; + private View mLeftSide, mStatusIcons, mBattery, mClock; private Animator mCurrentAnimation; public PhoneStatusBarTransitions(PhoneStatusBarView view) { @@ -46,7 +46,6 @@ public final class PhoneStatusBarTransitions extends BarTransitions { mLeftSide = mView.findViewById(R.id.status_bar_left_side); mStatusIcons = mView.findViewById(R.id.statusIcons); mBattery = mView.findViewById(R.id.battery); - mDivider = mView.findViewById(R.id.divider); applyModeBackground(-1, getMode(), false /*animate*/); applyMode(getMode(), false /*animate*/); } @@ -89,7 +88,6 @@ public final class PhoneStatusBarTransitions extends BarTransitions { anims.playTogether( animateTransitionTo(mLeftSide, newAlpha), animateTransitionTo(mStatusIcons, newAlpha), - animateTransitionTo(mDivider, newAlpha), animateTransitionTo(mBattery, newAlphaBC) ); if (isLightsOut(mode)) { @@ -100,8 +98,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions { } else { mLeftSide.setAlpha(newAlpha); mStatusIcons.setAlpha(newAlpha); - mDivider.setAlpha(newAlpha); mBattery.setAlpha(newAlphaBC); } } -}
\ No newline at end of file +} 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 ffbbffc0d8d9..f3b0a79f9518 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -25,7 +25,6 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; -import android.provider.DeviceConfig; import android.util.AttributeSet; import android.util.EventLog; import android.util.Pair; @@ -40,8 +39,6 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.systemui.DarkReceiverImpl; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; import com.android.systemui.R; @@ -81,9 +78,6 @@ public class PhoneStatusBarView extends PanelBar { @Nullable private DisplayCutout mDisplayCutout; - private DarkReceiverImpl mSplitDivider; - private View mDividerContainer; - private QsSplitPropertyListener mPropertyListener; /** * Draw this many pixels into the left/right side of the cutout to optimally use the space */ @@ -115,10 +109,6 @@ public class PhoneStatusBarView extends PanelBar { mBattery = findViewById(R.id.battery); mCutoutSpace = findViewById(R.id.cutout_space_view); mCenterIconSpace = findViewById(R.id.centered_icon_area); - mSplitDivider = findViewById(R.id.divider); - mDividerContainer = findViewById(R.id.divider_container); - maybeShowDivider(true); - mPropertyListener = new QsSplitPropertyListener(mDividerContainer); updateResources(); } @@ -128,26 +118,16 @@ public class PhoneStatusBarView extends PanelBar { super.onAttachedToWindow(); // Always have Battery meters in the status bar observe the dark/light modes. Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); - Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSplitDivider); - maybeShowDivider(true); if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) { updateLayoutForCutout(); } - if (mPropertyListener != null) { - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mContext.getMainExecutor(), mPropertyListener); - } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); - Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mSplitDivider); mDisplayCutout = null; - if (mPropertyListener != null) { - DeviceConfig.removeOnPropertiesChangedListener(mPropertyListener); - } } @Override @@ -216,7 +196,6 @@ public class PhoneStatusBarView extends PanelBar { public void onPanelPeeked() { super.onPanelPeeked(); mBar.makeExpandedVisible(false); - maybeShowDivider(!mBar.mPanelExpanded); } @Override @@ -225,7 +204,6 @@ public class PhoneStatusBarView extends PanelBar { // Close the status bar in the next frame so we can show the end of the animation. post(mHideExpandedRunnable); mIsFullyOpenedPanel = false; - maybeShowDivider(!mBar.mPanelExpanded); } public void removePendingHideExpandedRunnables() { @@ -239,7 +217,6 @@ public class PhoneStatusBarView extends PanelBar { mPanel.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } mIsFullyOpenedPanel = true; - maybeShowDivider(!mBar.mPanelExpanded); } @Override @@ -263,28 +240,24 @@ public class PhoneStatusBarView extends PanelBar { mBar.onTrackingStarted(); mScrimController.onTrackingStarted(); removePendingHideExpandedRunnables(); - maybeShowDivider(!mBar.mPanelExpanded); } @Override public void onClosingFinished() { super.onClosingFinished(); mBar.onClosingFinished(); - maybeShowDivider(!mBar.mPanelExpanded); } @Override public void onTrackingStopped(boolean expand) { super.onTrackingStopped(expand); mBar.onTrackingStopped(expand); - maybeShowDivider(!mBar.mPanelExpanded); } @Override public void onExpandingFinished() { super.onExpandingFinished(); mScrimController.onExpandingFinished(); - maybeShowDivider(!mBar.mPanelExpanded); } @Override @@ -416,31 +389,4 @@ public class PhoneStatusBarView extends PanelBar { protected boolean shouldPanelBeVisible() { return mHeadsUpVisible || super.shouldPanelBeVisible(); } - - void maybeShowDivider(boolean showDivider) { - int state = - showDivider && NotificationPanelViewController.isQsSplitEnabled() - ? View.VISIBLE : View.GONE; - mDividerContainer.setVisibility(state); - } - - private static class QsSplitPropertyListener implements - DeviceConfig.OnPropertiesChangedListener { - private final View mDivider; - - QsSplitPropertyListener(View divider) { - mDivider = divider; - } - - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (properties.getNamespace().equals(DeviceConfig.NAMESPACE_SYSTEMUI) - && properties.getKeyset().contains( - SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED)) { - boolean splitEnabled = properties.getBoolean( - SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false); - mDivider.setVisibility(splitEnabled ? VISIBLE : GONE); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1726b4884aff..c68d9942419b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -168,12 +168,10 @@ import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.EmptyShadeView; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -191,15 +189,11 @@ import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationClicker; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; -import com.android.systemui.statusbar.notification.NotificationListController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -218,7 +212,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -356,7 +349,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final Object mQueueLock = new Object(); - private final FeatureFlags mFeatureFlags; private final StatusBarIconController mIconController; private final PulseExpansionHandler mPulseExpansionHandler; private final NotificationWakeUpCoordinator mWakeUpCoordinator; @@ -365,7 +357,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final HeadsUpManagerPhone mHeadsUpManager; private final DynamicPrivacyController mDynamicPrivacyController; private final BypassHeadsUpNotifier mBypassHeadsUpNotifier; - private final Lazy<NotifPipelineInitializer> mNewNotifPipeline; private final FalsingManager mFalsingManager; private final BroadcastDispatcher mBroadcastDispatcher; private final ConfigurationController mConfigurationController; @@ -374,7 +365,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder; private final PluginManager mPluginManager; - private final RemoteInputUriController mRemoteInputUriController; private final Optional<Divider> mDividerOptional; private final StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; @@ -387,8 +377,8 @@ public class StatusBar extends SystemUI implements DemoMode, private final KeyguardDismissUtil mKeyguardDismissUtil; private final ExtensionController mExtensionController; private final UserInfoControllerImpl mUserInfoControllerImpl; - private final NotificationRowBinderImpl mNotificationRowBinder; private final DismissCallbackRegistry mDismissCallbackRegistry; + private NotificationsController mNotificationsController; // expanded notifications // the sliding/resizing panel within the notification window @@ -412,8 +402,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; - private final NotificationEntryManager mEntryManager; - private NotificationListController mNotificationListController; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final NotificationViewHierarchyManager mViewHierarchyManager; private final KeyguardViewMediator mKeyguardViewMediator; @@ -589,7 +577,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onStrongAuthStateChanged(int userId) { super.onStrongAuthStateChanged(userId); - mEntryManager.updateNotifications("onStrongAuthStateChanged"); + mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged"); } }; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); @@ -614,7 +602,7 @@ public class StatusBar extends SystemUI implements DemoMode, @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public StatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -626,13 +614,11 @@ public class StatusBar extends SystemUI implements DemoMode, HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -653,12 +639,10 @@ public class StatusBar extends SystemUI implements DemoMode, VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -676,7 +660,6 @@ public class StatusBar extends SystemUI implements DemoMode, Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, LightsOutNotifController lightsOutNotifController, StatusBarNotificationActivityStarter.Builder @@ -692,10 +675,9 @@ public class StatusBar extends SystemUI implements DemoMode, KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { super(context); - mFeatureFlags = featureFlags; + mNotificationsController = notificationsController; mLightBarController = lightBarController; mAutoHideController = autoHideController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -707,13 +689,11 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager = headsUpManagerPhone; mDynamicPrivacyController = dynamicPrivacyController; mBypassHeadsUpNotifier = bypassHeadsUpNotifier; - mNewNotifPipeline = newNotifPipeline; mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler; mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; - mEntryManager = notificationEntryManager; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mViewHierarchyManager = notificationViewHierarchyManager; mKeyguardViewMediator = keyguardViewMediator; @@ -734,12 +714,10 @@ public class StatusBar extends SystemUI implements DemoMode, mVibratorHelper = vibratorHelper; mBubbleController = bubbleController; mGroupManager = groupManager; - mGroupAlertTransferHelper = groupAlertTransferHelper; mVisualStabilityManager = visualStabilityManager; mDeviceProvisionedController = deviceProvisionedController; mNavigationBarController = navigationBarController; mAssistManagerLazy = assistManagerLazy; - mNotificationListener = notificationListener; mConfigurationController = configurationController; mNotificationShadeWindowController = notificationShadeWindowController; mLockscreenLockIconController = lockscreenLockIconController; @@ -757,7 +735,6 @@ public class StatusBar extends SystemUI implements DemoMode, mRecentsOptional = recentsOptional; mStatusBarComponentBuilder = statusBarComponentBuilder; mPluginManager = pluginManager; - mRemoteInputUriController = remoteInputUriController; mDividerOptional = dividerOptional; mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder; mShadeController = shadeController; @@ -771,12 +748,11 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardDismissUtil = keyguardDismissUtil; mExtensionController = extensionController; mUserInfoControllerImpl = userInfoControllerImpl; - mNotificationRowBinder = notificationRowBinder; mDismissCallbackRegistry = dismissCallbackRegistry; mBubbleExpandListener = (isExpanding, key) -> { - mEntryManager.updateNotifications("onBubbleExpandChanged"); + mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged"); updateScrimController(); }; @@ -786,11 +762,10 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void start() { - mNotificationListener.registerAsSystemService(); mScreenLifecycle.addObserver(mScreenObserver); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mUiModeManager = mContext.getSystemService(UiModeManager.class); - mBypassHeadsUpNotifier.setUp(mEntryManager); + mBypassHeadsUpNotifier.setUp(); mBubbleController.setExpandListener(mBubbleExpandListener); mActivityIntentHelper = new ActivityIntentHelper(mContext); @@ -1060,12 +1035,8 @@ public class StatusBar extends SystemUI implements DemoMode, mConfigurationController.addCallback(mHeadsUpManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); - mHeadsUpManager.addListener(mGroupManager); - mHeadsUpManager.addListener(mGroupAlertTransferHelper); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - mGroupManager.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mNotificationLogger.setHeadsUpManager(mHeadsUpManager); createNavigationBar(result); @@ -1243,16 +1214,10 @@ public class StatusBar extends SystemUI implements DemoMode, mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController, mHeadsUpManager, mNotificationShadeWindowView, mStackScroller, mDozeScrimController, mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController, - mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController, + mNotificationAlertingManager, mKeyguardStateController, mKeyguardIndicationController, this /* statusBar */, mShadeController, mCommandQueue, mInitController); - mNotificationListController = - new NotificationListController( - mEntryManager, - (NotificationListContainer) mStackScroller, - mDeviceProvisionedController); - mNotificationShelf.setOnActivatedListener(mPresenter); mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController); @@ -1266,22 +1231,12 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - mNotificationRowBinder.setInflationCallback(mEntryManager); - } - - mRemoteInputUriController.attach(mEntryManager); - - mNotificationRowBinder.setNotificationClicker(new NotificationClicker( - Optional.of(this), mBubbleController, mNotificationActivityStarter)); - - mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); - mNotificationListController.bind(); - - if (mFeatureFlags.isNewNotifPipelineEnabled()) { - mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder); - } - mEntryManager.attach(mNotificationListener); + mNotificationsController.initialize( + this, + mPresenter, + (NotificationListContainer) mStackScroller, + mNotificationActivityStarter, + mPresenter); } /** @@ -1518,7 +1473,7 @@ public class StatusBar extends SystemUI implements DemoMode, * @param reason why we're requesting a notification update */ public void requestNotificationUpdate(String reason) { - mEntryManager.updateNotifications(reason); + mNotificationsController.requestNotificationUpdate(reason); } /** @@ -1726,7 +1681,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - mEntryManager.updateNotifications("onHeadsUpStateChanged"); + mNotificationsController.requestNotificationUpdate("onHeadsUpStateChanged"); if (mStatusBarStateController.isDozing() && isHeadsUp) { entry.setPulseSuppressed(false); mDozeServiceHost.fireNotificationPulse(entry); @@ -2485,11 +2440,9 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarKeyguardViewManager.dump(pw); } - if (DUMPTRUCK) { - synchronized (mEntryManager) { - mEntryManager.dump(pw, " "); - } + mNotificationsController.dump(fd, pw, args, DUMPTRUCK); + if (DUMPTRUCK) { if (false) { pw.println("see the logcat for a dump of the views we have created."); // must happen on ui thread @@ -2512,15 +2465,6 @@ public class StatusBar extends SystemUI implements DemoMode, } else { pw.println(" mHeadsUpManager: null"); } - if (mGroupManager != null) { - mGroupManager.dump(fd, pw, args); - } else { - pw.println(" mGroupManager: null"); - } - - if (mBubbleController != null) { - mBubbleController.dump(fd, pw, args); - } if (mLightBarController != null) { mLightBarController.dump(fd, pw, args); @@ -2747,9 +2691,7 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - for (NotificationEntry entry : mEntryManager.getVisibleNotifications()) { - entry.resetUserExpansion(); - } + mNotificationsController.resetUserExpandedStates(); } private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen) { @@ -2855,7 +2797,7 @@ public class StatusBar extends SystemUI implements DemoMode, try { // consider the transition from peek to expanded to be a panel open, // but not one that clears notification effects. - int notificationLoad = mEntryManager.getActiveNotificationsCount(); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); mBarService.onPanelRevealed(false, notificationLoad); } catch (RemoteException ex) { // Won't fail unless the world has ended. @@ -2873,7 +2815,7 @@ public class StatusBar extends SystemUI implements DemoMode, !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mEntryManager.getActiveNotificationsCount(); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { notificationLoad = 1; } @@ -3514,7 +3456,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateQsExpansionEnabled(); mKeyguardViewMediator.setDozing(mDozing); - mEntryManager.updateNotifications("onDozingChanged"); + mNotificationsController.requestNotificationUpdate("onDozingChanged"); updateDozingState(); mDozeServiceHost.updateDozing(); updateScrimController(); @@ -3965,7 +3907,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected ViewGroup mStackScroller; private final NotificationGroupManager mGroupManager; - private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper; // handling reordering private final VisualStabilityManager mVisualStabilityManager; @@ -4032,21 +3973,12 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private final NotificationListener mNotificationListener; - public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { - if (snoozeOption.getSnoozeCriterion() != null) { - mNotificationListener.snoozeNotification(sbn.getKey(), - snoozeOption.getSnoozeCriterion().getId()); - } else { - mNotificationListener.snoozeNotification(sbn.getKey(), - snoozeOption.getMinutesToSnoozeFor() * 60 * 1000); - } + mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); } public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) { - mNotificationListener.snoozeNotification(sbn.getKey(), - hoursToSnooze * 60 * 60 * 1000); + mNotificationsController.setNotificationSnoozed(sbn, hoursToSnooze); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 86a81ce3d1f1..6a046884e835 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.sNewInsetsMode; +import static android.view.WindowInsets.Type.navigationBars; import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_FADING; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -789,7 +792,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() { @Override public void run() { - mStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE); + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) { + mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() + .show(navigationBars()); + } else { + mStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE); + } } }; @@ -856,7 +864,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } else { mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable); - mStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE); + if (sNewInsetsMode == NEW_INSETS_MODE_FULL) { + mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() + .hide(navigationBars()); + } else { + mStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index 7615bf826287..15a0e08e285f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -46,9 +46,7 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -61,12 +59,10 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationAlertingManager; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -77,7 +73,6 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.volume.VolumeComponent; @@ -105,7 +100,7 @@ public class StatusBarModule { @Singleton static StatusBar provideStatusBar( Context context, - FeatureFlags featureFlags, + NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -117,13 +112,11 @@ public class StatusBarModule { HeadsUpManagerPhone headsUpManagerPhone, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, - Lazy<NotifPipelineInitializer> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, - NotificationEntryManager notificationEntryManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -144,12 +137,10 @@ public class StatusBarModule { VibratorHelper vibratorHelper, BubbleController bubbleController, NotificationGroupManager groupManager, - NotificationGroupAlertTransferHelper groupAlertTransferHelper, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, Lazy<AssistManager> assistManagerLazy, - NotificationListener notificationListener, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, LockscreenLockIconController lockscreenLockIconController, @@ -167,7 +158,6 @@ public class StatusBarModule { Optional<Recents> recentsOptional, Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, - RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, LightsOutNotifController lightsOutNotifController, StatusBarNotificationActivityStarter.Builder @@ -183,11 +173,10 @@ public class StatusBarModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - NotificationRowBinderImpl notificationRowBinder, DismissCallbackRegistry dismissCallbackRegistry) { return new StatusBar( context, - featureFlags, + notificationsController, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -199,13 +188,11 @@ public class StatusBarModule { headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier, - newNotifPipeline, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager, notificationLogger, - notificationEntryManager, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, @@ -226,12 +213,10 @@ public class StatusBarModule { vibratorHelper, bubbleController, groupManager, - groupAlertTransferHelper, visualStabilityManager, deviceProvisionedController, navigationBarController, assistManagerLazy, - notificationListener, configurationController, notificationShadeWindowController, lockscreenLockIconController, @@ -249,7 +234,6 @@ public class StatusBarModule { recentsOptional, statusBarComponentBuilder, pluginManager, - remoteInputUriController, dividerOptional, lightsOutNotifController, statusBarNotificationActivityStarterBuilder, @@ -264,7 +248,6 @@ public class StatusBarModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, - notificationRowBinder, dismissCallbackRegistry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 1336b2de82d3..2485513abba5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -141,7 +141,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, ActivityLaunchAnimator activityLaunchAnimator, DynamicPrivacyController dynamicPrivacyController, NotificationAlertingManager notificationAlertingManager, - NotificationRowBinderImpl notificationRowBinder, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, StatusBar statusBar, @@ -216,8 +215,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mEntryManager.addNotificationLifetimeExtender(mGutsManager); mEntryManager.addNotificationLifetimeExtenders( remoteInputManager.getLifetimeExtenders()); - notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, - this); mNotificationInterruptionStateProvider.setUpWithPresenter( this, mHeadsUpManager, this::canHeadsUp); mLockscreenUserManager.setUpWithPresenter(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 830b50e35490..8231f8b3a09b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -30,5 +30,6 @@ public interface HotspotController extends CallbackController<Callback>, Dumpabl interface Callback { void onHotspotChanged(boolean enabled, int numDevices); + default void onHotspotAvailabilityChanged(boolean available) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index df9c3f4d6e26..d0904049d85a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; +import android.net.TetheringManager; import android.net.wifi.WifiClient; import android.net.wifi.WifiManager; import android.os.Handler; @@ -26,6 +27,8 @@ import android.os.HandlerExecutor; import android.os.UserManager; import android.util.Log; +import com.android.internal.util.ConcurrentUtils; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import java.io.FileDescriptor; @@ -46,36 +49,63 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ArrayList<Callback> mCallbacks = new ArrayList<>(); - private final ConnectivityManager mConnectivityManager; + private final TetheringManager mTetheringManager; private final WifiManager mWifiManager; private final Handler mMainHandler; private final Context mContext; private int mHotspotState; private volatile int mNumConnectedDevices; + private volatile boolean mIsTetheringSupported; + private volatile boolean mHasTetherableWifiRegexs; private boolean mWaitingForTerminalState; + private TetheringManager.TetheringEventCallback mTetheringCallback = + new TetheringManager.TetheringEventCallback() { + @Override + public void onTetheringSupported(boolean supported) { + super.onTetheringSupported(supported); + if (mIsTetheringSupported != supported) { + mIsTetheringSupported = supported; + fireHotspotAvailabilityChanged(); + } + } + + @Override + public void onTetherableInterfaceRegexpsChanged( + TetheringManager.TetheringInterfaceRegexps reg) { + super.onTetherableInterfaceRegexpsChanged(reg); + final boolean newValue = reg.getTetherableWifiRegexs().size() != 0; + if (mHasTetherableWifiRegexs != newValue) { + mHasTetherableWifiRegexs = newValue; + fireHotspotAvailabilityChanged(); + } + } + }; + /** * Controller used to retrieve information related to a hotspot. */ @Inject - public HotspotControllerImpl(Context context, @Main Handler mainHandler) { + public HotspotControllerImpl(Context context, @Main Handler mainHandler, + @Background Handler backgroundHandler) { mContext = context; - mConnectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mTetheringManager = context.getSystemService(TetheringManager.class); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mMainHandler = mainHandler; + mTetheringManager.registerTetheringEventCallback( + new HandlerExecutor(backgroundHandler), mTetheringCallback); } @Override public boolean isHotspotSupported() { - return mConnectivityManager.isTetheringSupported() - && mConnectivityManager.getTetherableWifiRegexs().length != 0 + return mIsTetheringSupported && mHasTetherableWifiRegexs && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser()); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HotspotController state:"); + pw.print(" available="); pw.println(isHotspotSupported()); pw.print(" mHotspotState="); pw.println(stateToString(mHotspotState)); pw.print(" mNumConnectedDevices="); pw.println(mNumConnectedDevices); pw.print(" mWaitingForTerminalState="); pw.println(mWaitingForTerminalState); @@ -152,17 +182,18 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof if (enabled) { mWaitingForTerminalState = true; if (DEBUG) Log.d(TAG, "Starting tethering"); - mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI, false, - new ConnectivityManager.OnStartTetheringCallback() { + mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, + ConcurrentUtils.DIRECT_EXECUTOR, + new TetheringManager.StartTetheringCallback() { @Override - public void onTetheringFailed() { + public void onTetheringFailed(final int result) { if (DEBUG) Log.d(TAG, "onTetheringFailed"); maybeResetSoftApState(); fireHotspotChangedCallback(); } }); } else { - mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); + mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); } } @@ -177,10 +208,25 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof * (as it can be blocked). */ private void fireHotspotChangedCallback() { + List<Callback> list; synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices); - } + list = new ArrayList<>(mCallbacks); + } + for (Callback callback : list) { + callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices); + } + } + + /** + * Sends a hotspot available changed callback. + */ + private void fireHotspotAvailabilityChanged() { + List<Callback> list; + synchronized (mCallbacks) { + list = new ArrayList<>(mCallbacks); + } + for (Callback callback : list) { + callback.onHotspotAvailabilityChanged(isHotspotSupported()); } } @@ -206,7 +252,7 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof switch (mHotspotState) { case WifiManager.WIFI_AP_STATE_FAILED: // TODO(b/110697252): must be called to reset soft ap state after failure - mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); + mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); // Fall through case WifiManager.WIFI_AP_STATE_ENABLED: case WifiManager.WIFI_AP_STATE_DISABLED: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 2907cd41a0db..8dfcb0ac215d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -134,6 +134,8 @@ public class UserSwitcherController implements Dumpable { mBroadcastDispatcher.registerReceiver( mReceiver, filter, null /* handler */, UserHandle.SYSTEM); + mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); + mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class); filter = new IntentFilter(); @@ -258,22 +260,20 @@ public class UserSwitcherController implements Dumpable { && mUserManager.canAddMoreUsers(); boolean createIsRestricted = !addUsersWhenLocked; - if (!mSimpleUserSwitcher) { - if (guestRecord == null) { - if (canCreateGuest) { - guestRecord = new UserRecord(null /* info */, null /* picture */, - true /* isGuest */, false /* isCurrent */, - false /* isAddUser */, createIsRestricted, canSwitchUsers); - checkIfAddUserDisallowedByAdminOnly(guestRecord); - records.add(guestRecord); - } - } else { - int index = guestRecord.isCurrent ? 0 : records.size(); - records.add(index, guestRecord); + if (guestRecord == null) { + if (canCreateGuest) { + guestRecord = new UserRecord(null /* info */, null /* picture */, + true /* isGuest */, false /* isCurrent */, + false /* isAddUser */, createIsRestricted, canSwitchUsers); + checkIfAddUserDisallowedByAdminOnly(guestRecord); + records.add(guestRecord); } + } else { + int index = guestRecord.isCurrent ? 0 : records.size(); + records.add(index, guestRecord); } - if (!mSimpleUserSwitcher && canCreateUser) { + if (canCreateUser) { UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, false /* isGuest */, false /* isCurrent */, true /* isAddUser */, createIsRestricted, canSwitchUsers); @@ -562,8 +562,7 @@ public class UserSwitcherController implements Dumpable { private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { public void onChange(boolean selfChange) { - mSimpleUserSwitcher = Settings.Global.getInt(mContext.getContentResolver(), - SIMPLE_USER_SWITCHER_GLOBAL_SETTING, 0) != 0; + mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0; refreshUsers(UserHandle.USER_NULL); @@ -579,6 +578,7 @@ public class UserSwitcherController implements Dumpable { final UserRecord u = mUsers.get(i); pw.print(" "); pw.println(u.toString()); } + pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher); } public String getCurrentUserName(Context context) { @@ -717,6 +717,13 @@ public class UserSwitcherController implements Dumpable { } } + private boolean shouldUseSimpleUserSwitcher() { + int defaultSimpleUserSwitcher = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0; + return Settings.Global.getInt(mContext.getContentResolver(), + SIMPLE_USER_SWITCHER_GLOBAL_SETTING, defaultSimpleUserSwitcher) != 0; + } + public void startActivity(Intent intent) { mActivityStarter.startActivity(intent, true); } diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto index 08ae99ceb7a1..d940a6b5c460 100644 --- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto +++ b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto @@ -29,3 +29,29 @@ message EdgeBackGestureHandlerProto { optional bool allow_gesture = 1; } + +/* represents a file full of system ui trace entries. + Encoded, it should start with 0x9 0x53 0x59 0x53 0x55 0x49 0x54 0x52 0x43 (.SYSUITRC), such + that they can be easily identified. */ +message SystemUiTraceFileProto { + + /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L + (this is needed because enums have to be 32 bits and there's no nice way to put 64bit + constants into .proto files. */ + enum MagicNumber { + INVALID = 0; + MAGIC_NUMBER_L = 0x55535953; /* SYSU (little-endian ASCII) */ + MAGIC_NUMBER_H = 0x43525449; /* ITRC (little-endian ASCII) */ + } + + optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ + repeated SystemUiTraceEntryProto entry = 2; +} + +/* one system ui trace entry. */ +message SystemUiTraceEntryProto { + /* required: elapsed realtime in nanos since boot of when this entry was logged */ + optional fixed64 elapsed_realtime_nanos = 1; + + optional SystemUiTraceProto system_ui = 3; +} diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace_file.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace_file.proto deleted file mode 100644 index d1523ef13501..000000000000 --- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace_file.proto +++ /dev/null @@ -1,49 +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. - */ - -syntax = "proto2"; - -import "frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto"; - -package com.android.systemui.tracing; - -option java_multiple_files = true; - -/* represents a file full of system ui trace entries. - Encoded, it should start with 0x9 0x53 0x59 0x53 0x55 0x49 0x54 0x52 0x43 (.SYSUITRC), such - that they can be easily identified. */ -message SystemUiTraceFileProto { - - /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L - (this is needed because enums have to be 32 bits and there's no nice way to put 64bit - constants into .proto files. */ - enum MagicNumber { - INVALID = 0; - MAGIC_NUMBER_L = 0x55535953; /* SYSU (little-endian ASCII) */ - MAGIC_NUMBER_H = 0x43525449; /* ITRC (little-endian ASCII) */ - } - - optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ - repeated SystemUiTraceEntryProto entry = 2; -} - -/* one system ui trace entry. */ -message SystemUiTraceEntryProto { - /* required: elapsed realtime in nanos since boot of when this entry was logged */ - optional fixed64 elapsed_realtime_nanos = 1; - - optional SystemUiTraceProto system_ui = 3; -} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java index a60ca6201419..49ada1a5e41e 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java @@ -158,7 +158,7 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference String demoTime = "1010"; // 10:10, a classic choice of horologists try { - String[] versionParts = android.os.Build.VERSION.RELEASE.split("\\."); + String[] versionParts = android.os.Build.VERSION.RELEASE_OR_CODENAME.split("\\."); int majorVersion = Integer.valueOf(versionParts[0]); demoTime = String.format("%02d00", majorVersion % 24); } catch (IllegalArgumentException ex) { diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java new file mode 100644 index 000000000000..264ddc026781 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java @@ -0,0 +1,28 @@ +/* + * 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.tv; + +import com.android.systemui.dagger.SystemUIRootComponent; + +import dagger.Binds; +import dagger.Module; + +@Module +interface TvSystemUIBinder { + @Binds + SystemUIRootComponent bindSystemUIRootComponent(TvSystemUIRootComponent systemUIRootComponent); +} diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java new file mode 100644 index 000000000000..7d3ec678fd5f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java @@ -0,0 +1,35 @@ +/* + * 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.tv; + +import android.content.Context; + +import com.android.systemui.SystemUIFactory; +import com.android.systemui.dagger.SystemUIRootComponent; + +/** + * TV variant {@link SystemUIFactory}, that substitutes default {@link SystemUIRootComponent} for + * {@link TvSystemUIRootComponent} + */ +public class TvSystemUIFactory extends SystemUIFactory { + @Override + protected SystemUIRootComponent buildSystemUIRootComponent(Context context) { + return DaggerTvSystemUIRootComponent.builder() + .context(context) + .build(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java new file mode 100644 index 000000000000..fcf27009883a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIRootComponent.java @@ -0,0 +1,58 @@ +/* + * 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.tv; + +import android.content.Context; + +import com.android.systemui.dagger.DefaultComponentBinder; +import com.android.systemui.dagger.DependencyBinder; +import com.android.systemui.dagger.DependencyProvider; +import com.android.systemui.dagger.SystemServicesModule; +import com.android.systemui.dagger.SystemUIBinder; +import com.android.systemui.dagger.SystemUIDefaultModule; +import com.android.systemui.dagger.SystemUIModule; +import com.android.systemui.dagger.SystemUIRootComponent; + +import javax.inject.Singleton; + +import dagger.BindsInstance; +import dagger.Component; + +/** + * Root component for Dagger injection. + */ +@Singleton +@Component(modules = { + DefaultComponentBinder.class, + DependencyProvider.class, + DependencyBinder.class, + SystemServicesModule.class, + SystemUIBinder.class, + SystemUIModule.class, + SystemUIDefaultModule.class, + TvSystemUIBinder.class}) +public interface TvSystemUIRootComponent extends SystemUIRootComponent { + /** + * Component Builder interface. This allows to bind Context instance in the component + */ + @Component.Builder + interface Builder { + @BindsInstance Builder context(Context context); + + TvSystemUIRootComponent build(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt new file mode 100644 index 000000000000..70bcc21426b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt @@ -0,0 +1,320 @@ +package com.android.systemui.util + +import android.graphics.Rect +import android.util.Log +import com.android.systemui.util.FloatingContentCoordinator.FloatingContent +import java.util.HashMap +import javax.inject.Inject +import javax.inject.Singleton + +/** Tag for debug logging. */ +private const val TAG = "FloatingCoordinator" + +/** + * Coordinates the positions and movement of floating content, such as PIP and Bubbles, to ensure + * that they don't overlap. If content does overlap due to content appearing or moving, the + * coordinator will ask content to move to resolve the conflict. + * + * After implementing [FloatingContent], content should call [onContentAdded] to begin coordination. + * Subsequently, call [onContentMoved] whenever the content moves, and the coordinator will move + * other content out of the way. [onContentRemoved] should be called when the content is removed or + * no longer visible. + */ +@Singleton +class FloatingContentCoordinator @Inject constructor() { + + /** + * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods + * that allow the [FloatingContentCoordinator] to determine the current location of the content, + * as well as the ability to ask it to move out of the way of other content. + * + * The default implementation of [calculateNewBoundsOnOverlap] moves the content up or down, + * depending on the position of the conflicting content. You can override this method if you + * want your own custom conflict resolution logic. + */ + interface FloatingContent { + + /** + * Return the bounds claimed by this content. This should include the bounds occupied by the + * content itself, as well as any padding, if desired. The coordinator will ensure that no + * other content is located within these bounds. + * + * If the content is animating, this method should return the bounds to which the content is + * animating. If that animation is cancelled, or updated, be sure that your implementation + * of this method returns the appropriate bounds, and call [onContentMoved] so that the + * coordinator moves other content out of the way. + */ + fun getFloatingBoundsOnScreen(): Rect + + /** + * Return the area within which this floating content is allowed to move. When resolving + * conflicts, the coordinator will never ask your content to move to a position where any + * part of the content would be out of these bounds. + */ + fun getAllowedFloatingBoundsRegion(): Rect + + /** + * Called when the coordinator needs this content to move to the given bounds. It's up to + * you how to do that. + * + * Note that if you start an animation to these bounds, [getFloatingBoundsOnScreen] should + * return the destination bounds, not the in-progress animated bounds. This is so the + * coordinator knows where floating content is going to be and can resolve conflicts + * accordingly. + */ + fun moveToBounds(bounds: Rect) + + /** + * Called by the coordinator when it needs to find a new home for this floating content, + * because a new or moving piece of content is now overlapping with it. + * + * [findAreaForContentVertically] and [findAreaForContentAboveOrBelow] are helpful utility + * functions that will find new bounds for your content automatically. Unless you require + * specific conflict resolution logic, these should be sufficient. By default, this method + * delegates to [findAreaForContentVertically]. + * + * @param overlappingContentBounds The bounds of the other piece of content, which + * necessitated this content's relocation. Your new position must not overlap with these + * bounds. + * @param otherContentBounds The bounds of any other pieces of floating content. Your new + * position must not overlap with any of these either. These bounds are guaranteed to be + * non-overlapping. + * @return The new bounds for this content. + */ + @JvmDefault + fun calculateNewBoundsOnOverlap( + overlappingContentBounds: Rect, + otherContentBounds: List<Rect> + ): Rect { + return findAreaForContentVertically( + getFloatingBoundsOnScreen(), + overlappingContentBounds, + otherContentBounds, + getAllowedFloatingBoundsRegion()) + } + } + + /** The bounds of all pieces of floating content added to the coordinator. */ + private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap() + + /** + * Makes the coordinator aware of a new piece of floating content, and moves any existing + * content out of the way, if necessary. + * + * If you don't want your new content to move existing content, use [getOccupiedBounds] to find + * an unoccupied area, and move the content there before calling this method. + */ + fun onContentAdded(newContent: FloatingContent) { + updateContentBounds() + allContentBounds[newContent] = newContent.getFloatingBoundsOnScreen() + maybeMoveConflictingContent(newContent) + } + + /** + * Called to notify the coordinator that a piece of floating content has moved (or is animating) + * to a new position, and that any conflicting floating content should be moved out of the way. + * + * The coordinator will call [FloatingContent.getFloatingBoundsOnScreen] to find the new bounds + * for the moving content. If you're animating the content, be sure that your implementation of + * getFloatingBoundsOnScreen returns the bounds to which it's animating, not the content's + * current bounds. + * + * If the animation moving this content is cancelled or updated, you'll need to call this method + * again, to ensure that content is moved out of the way of the latest bounds. + * + * @param content The content that has moved. + */ + @JvmOverloads + fun onContentMoved(content: FloatingContent) { + if (!allContentBounds.containsKey(content)) { + Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " + + "This should never happen.") + return + } + + updateContentBounds() + maybeMoveConflictingContent(content) + } + + /** + * Called to notify the coordinator that a piece of floating content has been removed or is no + * longer visible. + */ + fun onContentRemoved(removedContent: FloatingContent) { + allContentBounds.remove(removedContent) + } + + /** + * Returns a set of Rects that represent the bounds of all of the floating content on the + * screen. + * + * [onContentAdded] will move existing content out of the way if the added content intersects + * existing content. That's fine - but if your specific starting position is not important, you + * can use this function to find unoccupied space for your content before calling + * [onContentAdded], so that moving existing content isn't necessary. + */ + fun getOccupiedBounds(): Collection<Rect> { + return allContentBounds.values + } + + /** + * Identifies any pieces of content that are now overlapping with the given content, and asks + * them to move out of the way. + */ + private fun maybeMoveConflictingContent(fromContent: FloatingContent) { + val conflictingNewBounds = allContentBounds[fromContent]!! + allContentBounds + // Filter to content that intersects with the new bounds. That's content that needs + // to move. + .filter { (content, bounds) -> + content != fromContent && Rect.intersects(conflictingNewBounds, bounds) } + // Tell that content to get out of the way, and save the bounds it says it's moving + // (or animating) to. + .forEach { (content, bounds) -> + content.moveToBounds( + content.calculateNewBoundsOnOverlap( + conflictingNewBounds, + // Pass all of the content bounds except the bounds of the + // content we're asking to move, and the conflicting new bounds + // (since those are passed separately). + otherContentBounds = allContentBounds.values + .minus(bounds) + .minus(conflictingNewBounds))) + allContentBounds[content] = content.getFloatingBoundsOnScreen() + } + } + + /** + * Update [allContentBounds] by calling [FloatingContent.getFloatingBoundsOnScreen] for all + * content and saving the result. + */ + private fun updateContentBounds() { + allContentBounds.keys.forEach { allContentBounds[it] = it.getFloatingBoundsOnScreen() } + } + + companion object { + /** + * Finds new bounds for the given content, either above or below its current position. The + * new bounds won't intersect with the newly overlapping rect or the exclusion rects, and + * will be within the allowed bounds unless no possible position exists. + * + * You can use this method to help find a new position for your content when the coordinator + * calls [FloatingContent.moveToAreaExcluding]. + * + * @param contentRect The bounds of the content for which we're finding a new home. + * @param newlyOverlappingRect The bounds of the content that forced this relocation by + * intersecting with the content we now need to move. If the overlapping content is + * overlapping the top half of this content, we'll try to move this content downward if + * possible (since the other content is 'pushing' it down), and vice versa. + * @param exclusionRects Any other areas that we need to avoid when finding a new home for + * the content. These areas must be non-overlapping with each other. + * @param allowedBounds The area within which we're allowed to find new bounds for the + * content. + * @return New bounds for the content that don't intersect the exclusion rects or the + * newly overlapping rect, and that is within bounds unless no possible in-bounds position + * exists. + */ + @JvmStatic + fun findAreaForContentVertically( + contentRect: Rect, + newlyOverlappingRect: Rect, + exclusionRects: Collection<Rect>, + allowedBounds: Rect + ): Rect { + // If the newly overlapping Rect's center is above the content's center, we'll prefer to + // find a space for this content that is below the overlapping content, since it's + // 'pushing' it down. This may not be possible due to to screen bounds, in which case + // we'll find space in the other direction. + val overlappingContentPushingDown = + newlyOverlappingRect.centerY() < contentRect.centerY() + + // Filter to exclusion rects that are above or below the content that we're finding a + // place for. Then, split into two lists - rects above the content, and rects below it. + var (rectsToAvoidAbove, rectsToAvoidBelow) = exclusionRects + .filter { rectToAvoid -> rectsIntersectVertically(rectToAvoid, contentRect) } + .partition { rectToAvoid -> rectToAvoid.top < contentRect.top } + + // Lazily calculate the closest possible new tops for the content, above and below its + // current location. + val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect), + findAbove = true) } + val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow( + contentRect, + exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect), + findAbove = false) } + + val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) } + val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) } + + // Use the 'below' position if the content is being overlapped from the top, unless it's + // out of bounds. Also use it if the content is being overlapped from the bottom, but + // the 'above' position is out of bounds. Otherwise, use the 'above' position. + val usePositionBelow = + overlappingContentPushingDown && positionBelowInBounds || + !overlappingContentPushingDown && !positionAboveInBounds + + // Return the content rect, but offset to reflect the new position. + return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove + } + + /** + * Finds a new position for the given content, either above or below its current position + * depending on whether [findAbove] is true or false, respectively. This new position will + * not intersect with any of the [exclusionRects]. + * + * This method is useful as a helper method for implementing your own conflict resolution + * logic. Otherwise, you'd want to use [findAreaForContentVertically], which takes screen + * bounds and conflicting bounds' location into account when deciding whether to move to new + * bounds above or below the current bounds. + * + * @param contentRect The content we're finding an area for. + * @param exclusionRects The areas we need to avoid when finding a new area for the content. + * These areas must be non-overlapping with each other. + * @param findAbove Whether we are finding an area above the content's current position, + * rather than an area below it. + */ + fun findAreaForContentAboveOrBelow( + contentRect: Rect, + exclusionRects: Collection<Rect>, + findAbove: Boolean + ): Rect { + // Sort the rects, since we want to move the content as little as possible. We'll + // start with the rects closest to the content and move outward. If we're finding an + // area above the content, that means we sort in reverse order to search the rects + // from highest to lowest y-value. + val sortedExclusionRects = + exclusionRects.sortedBy { if (findAbove) -it.top else it.top } + + val proposedNewBounds = Rect(contentRect) + for (exclusionRect in sortedExclusionRects) { + // If the proposed new bounds don't intersect with this exclusion rect, that + // means there's room for the content here. We know this because the rects are + // sorted and non-overlapping, so any subsequent exclusion rects would be higher + // (or lower) than this one and can't possibly intersect if this one doesn't. + if (!Rect.intersects(proposedNewBounds, exclusionRect)) { + break + } else { + // Otherwise, we need to keep searching for new bounds. If we're finding an + // area above, propose new bounds that place the content just above the + // exclusion rect. If we're finding an area below, propose new bounds that + // place the content just below the exclusion rect. + val verticalOffset = + if (findAbove) -contentRect.height() else exclusionRect.height() + proposedNewBounds.offsetTo( + proposedNewBounds.left, + exclusionRect.top + verticalOffset) + } + } + + return proposedNewBounds + } + + /** Returns whether or not the two Rects share any of the same space on the X axis. */ + private fun rectsIntersectVertically(r1: Rect, r2: Rect): Boolean { + return (r1.left >= r2.left && r1.left <= r2.right) || + (r1.right <= r2.right && r1.right >= r2.left) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 47454cb5aca1..cfa2947eb862 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -129,8 +129,6 @@ public class Utils { */ public static boolean useQsMediaPlayer(Context context) { int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 0); - flag |= Settings.System.getInt(context.getContentResolver(), "npv_plugin_flag", 0); - return flag > 0; } } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index cfd77be9303d..f4157f21e158 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -901,6 +901,23 @@ class PhysicsAnimator<T> private constructor (val target: T) { verboseLogging = debug } + /** + * Estimates the end value of a fling that starts at the given value using the provided + * start velocity and fling configuration. + * + * This is only an estimate. Fling animations use a timing-based physics simulation that is + * non-deterministic, so this exact value may not be reached. + */ + @JvmStatic + fun estimateFlingEndValue( + startValue: Float, + startVelocity: Float, + flingConfig: FlingConfig + ): Float { + val distance = startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER) + return Math.min(flingConfig.max, Math.max(flingConfig.min, startValue + distance)) + } + @JvmStatic fun getReadablePropertyName(property: FloatPropertyCompat<*>): String { return when (property) { diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java index 264a683f6a3d..64b0b66a47a9 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java @@ -31,9 +31,11 @@ import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; +import android.graphics.Insets; import android.graphics.Rect; import android.os.SystemProperties; import android.provider.Settings; +import android.util.RotationUtils; import android.util.Size; import android.view.Display; import android.view.DisplayCutout; @@ -43,8 +45,6 @@ import android.view.Surface; import com.android.internal.R; -import java.util.List; - /** * Contains information about the layout-properties of a display. This refers to internal layout * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to @@ -323,28 +323,38 @@ public class DisplayLayout { if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) { return null; } + final Insets waterfallInsets = + RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); if (rotation == ROTATION_0) { - return computeSafeInsets( - cutout, displayWidth, displayHeight); + return computeSafeInsets(cutout, displayWidth, displayHeight); } final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - Rect[] cutoutRects = computeSafeInsets(cutout, displayWidth, displayHeight) - .getBoundingRectsAll(); + Rect[] cutoutRects = cutout.getBoundingRectsAll(); final Rect[] newBounds = new Rect[cutoutRects.length]; final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight); for (int i = 0; i < cutoutRects.length; ++i) { - newBounds[i] = new Rect(cutoutRects[i]); - rotateBounds(newBounds[i], displayBounds, rotation); + final Rect rect = new Rect(cutoutRects[i]); + if (!rect.isEmpty()) { + rotateBounds(rect, displayBounds, rotation); + } + newBounds[getBoundIndexFromRotation(i, rotation)] = rect; } - return computeSafeInsets(DisplayCutout.fromBounds(newBounds), + return computeSafeInsets( + DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets), rotated ? displayHeight : displayWidth, rotated ? displayWidth : displayHeight); } + private static int getBoundIndexFromRotation(int index, int rotation) { + return (index - rotation) < 0 + ? index - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH + : index - rotation; + } + /** Calculate safe insets. */ public static DisplayCutout computeSafeInsets(DisplayCutout inner, int displayWidth, int displayHeight) { - if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) { + if (inner == DisplayCutout.NO_CUTOUT) { return null; } @@ -353,58 +363,44 @@ public class DisplayLayout { return inner.replaceSafeInsets(safeInsets); } - private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) { - if (displaySize.getWidth() < displaySize.getHeight()) { - final List<Rect> boundingRects = cutout.replaceSafeInsets( - new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2)) - .getBoundingRects(); - int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP); - int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM); - return new Rect(0, topInset, 0, bottomInset); - } else if (displaySize.getWidth() > displaySize.getHeight()) { - final List<Rect> boundingRects = cutout.replaceSafeInsets( - new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0)) - .getBoundingRects(); - int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT); - int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT); - return new Rect(leftInset, 0, right, 0); - } else { + private static Rect computeSafeInsets( + Size displaySize, DisplayCutout cutout) { + if (displaySize.getWidth() == displaySize.getHeight()) { throw new UnsupportedOperationException("not implemented: display=" + displaySize + " cutout=" + cutout); } + + int leftInset = Math.max(cutout.getWaterfallInsets().left, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT)); + int topInset = Math.max(cutout.getWaterfallInsets().top, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP)); + int rightInset = Math.max(cutout.getWaterfallInsets().right, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT)); + int bottomInset = Math.max(cutout.getWaterfallInsets().bottom, + findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(), + Gravity.BOTTOM)); + + return new Rect(leftInset, topInset, rightInset, bottomInset); } - private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) { + private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) { + if (boundingRect.isEmpty()) { + return 0; + } + int inset = 0; - final int size = boundingRects.size(); - for (int i = 0; i < size; i++) { - Rect boundingRect = boundingRects.get(i); - switch (gravity) { - case Gravity.TOP: - if (boundingRect.top == 0) { - inset = Math.max(inset, boundingRect.bottom); - } - break; - case Gravity.BOTTOM: - if (boundingRect.bottom == display.getHeight()) { - inset = Math.max(inset, display.getHeight() - boundingRect.top); - } - break; - case Gravity.LEFT: - if (boundingRect.left == 0) { - inset = Math.max(inset, boundingRect.right); - } - break; - case Gravity.RIGHT: - if (boundingRect.right == display.getWidth()) { - inset = Math.max(inset, display.getWidth() - boundingRect.left); - } - break; - default: - throw new IllegalArgumentException("unknown gravity: " + gravity); - } + switch (gravity) { + case Gravity.TOP: + return Math.max(inset, boundingRect.bottom); + case Gravity.BOTTOM: + return Math.max(inset, display.getHeight() - boundingRect.top); + case Gravity.LEFT: + return Math.max(inset, boundingRect.right); + case Gravity.RIGHT: + return Math.max(inset, display.getWidth() - boundingRect.left); + default: + throw new IllegalArgumentException("unknown gravity: " + gravity); } - return inset; } static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java index 1954b3936376..0e9a245d5be6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java @@ -39,7 +39,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceView; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -54,7 +54,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; @RunWithLooper @RunWith(AndroidTestingRunner.class) @@ -77,8 +76,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { private KeyguardSecurityCallback mKeyguardCallback; @Mock private KeyguardUpdateMonitor mUpdateMonitor; - @Spy - private StubTransaction mTransaction; + @Mock + private SurfaceControlViewHost.SurfacePackage mSurfacePackage; @Before public void setUp() { @@ -97,21 +96,20 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient); mTestController = new AdminSecondaryLockScreenController( - mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler, mTransaction); + mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler); } @Test public void testShow() throws Exception { doAnswer(invocation -> { IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1]; - callback.onSurfaceControlCreated(new SurfaceControl()); + callback.onRemoteContentReady(mSurfacePackage); return null; }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class)); mTestController.show(mServiceIntent); verifySurfaceReady(); - verify(mTransaction).reparent(any(), any()); assertThat(mContext.isBound(mComponentName)).isTrue(); } @@ -133,7 +131,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { // Show the view first, then hide. doAnswer(invocation -> { IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1]; - callback.onSurfaceControlCreated(new SurfaceControl()); + callback.onRemoteContentReady(mSurfacePackage); return null; }).when(mKeyguardClient).onSurfaceReady(any(), any(IKeyguardCallback.class)); @@ -189,19 +187,4 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase { verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID); assertThat(mContext.isBound(mComponentName)).isFalse(); } - - /** - * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit testing to - * avoid calls to native code. - */ - private class StubTransaction extends SurfaceControl.Transaction { - @Override - public void apply() { - } - - @Override - public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) { - return this; - } - } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 795cbb92fefe..7e4ba92301aa 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -437,14 +437,14 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testIgnoresAuth_whenLockout() { + public void testTriesToAuthenticate_whenLockout() { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); mTestableLooper.processAllMessages(); when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); - verify(mFaceManager, never()).authenticate(any(), any(), anyInt(), any(), any(), anyInt()); + verify(mFaceManager).authenticate(any(), any(), anyInt(), any(), any(), anyInt()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 364ee666e17d..ffe8c285b4f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -31,8 +31,8 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.util.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java index f2642594802d..c6c7b87da544 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java @@ -36,6 +36,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; import android.os.Bundle; +import android.os.IBinder; import android.os.UserManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -43,6 +44,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ScrollView; @@ -175,6 +177,28 @@ public class AuthContainerViewTest extends SysuiTestCase { assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType); } + @Test + public void testCredentialUI_disablesClickingOnBackground() { + // In the credential view, clicking on the background (to cancel authentication) is not + // valid. Thus, the listener should be null, and it should not be in the accessibility + // hierarchy. + initializeContainer(Authenticators.DEVICE_CREDENTIAL); + + mAuthContainer.onAttachedToWindowInternal(); + + verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null)); + verify(mAuthContainer.mBackgroundView).setImportantForAccessibility( + eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO)); + } + + @Test + public void testLayoutParams_hasSecureWindowFlag() { + final IBinder windowToken = mock(IBinder.class); + final WindowManager.LayoutParams layoutParams = + AuthContainerView.getLayoutParams(windowToken); + assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0); + } + private void initializeContainer(int authenticators) { AuthContainerView.Config config = new AuthContainerView.Config(); config.mContext = mContext; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 5cfb4b39b16b..c3b55e2ec168 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -20,8 +20,7 @@ import static android.app.Notification.FLAG_BUBBLE; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; - -import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; +import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; import static com.google.common.truth.Truth.assertThat; @@ -34,6 +33,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -55,23 +55,26 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.DumpController; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoveInterceptor; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -81,7 +84,6 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.InjectionInflationController; @@ -93,6 +95,13 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + +/** + * Tests the NotificationEntryManager setup with BubbleController. + * The {@link NotifPipeline} setup with BubbleController is tested in + * {@link NewNotifPipelineBubbleControllerTest}. + */ @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -152,9 +161,13 @@ public class BubbleControllerTest extends SysuiTestCase { @Mock private ShadeController mShadeController; @Mock - private RemoteInputUriController mRemoteInputUriController; - @Mock private NotificationRowComponent mNotificationRowComponent; + @Mock + private NotifPipeline mNotifPipeline; + @Mock + private FeatureFlags mFeatureFlagsOldPipeline; + @Mock + private DumpController mDumpController; private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; @@ -216,6 +229,7 @@ public class BubbleControllerTest extends SysuiTestCase { mock(HeadsUpManager.class), mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); + when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mBubbleController = new TestableBubbleController(mContext, mNotificationShadeWindowController, mStatusBarStateController, @@ -227,7 +241,9 @@ public class BubbleControllerTest extends SysuiTestCase { mLockscreenUserManager, mNotificationGroupManager, mNotificationEntryManager, - mRemoteInputUriController); + mNotifPipeline, + mFeatureFlagsOldPipeline, + mDumpController); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); @@ -237,7 +253,7 @@ public class BubbleControllerTest extends SysuiTestCase { mEntryListener = mEntryListenerCaptor.getValue(); // And the remove interceptor verify(mNotificationEntryManager, atLeastOnce()) - .setNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture()); + .addNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture()); mRemoveInterceptor = mRemoveInterceptorCaptor.getValue(); } @@ -265,7 +281,7 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleStateChangeListener).onHasBubblesChanged(true); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertFalse(mNotificationShadeWindowController.getBubblesShowing()); assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); verify(mNotificationEntryManager, times(2)).updateNotifications(anyString()); @@ -274,7 +290,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testRemoveBubble_withDismissedNotif() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -286,12 +302,12 @@ public class BubbleControllerTest extends SysuiTestCase { // Now remove the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); // Since the notif is dismissed, once the bubble is removed, performRemoveNotification gets // called to really remove the notif verify(mNotificationEntryManager, times(1)).performRemoveNotification( - mRow.getEntry().getSbn(), UNDEFINED_DISMISS_REASON); + eq(mRow.getEntry().getSbn()), anyInt()); assertFalse(mBubbleController.hasBubbles()); } @@ -317,7 +333,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertFalse(mBubbleController.isStackExpanded()); // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // We should have bubbles & their notifs should not be suppressed @@ -347,8 +363,8 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); - mEntryListener.onNotificationAdded(mRow2.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.updateBubble(mRow2.getEntry()); @@ -390,7 +406,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testExpansionRemovesShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // We should have bubbles & their notifs should not be suppressed @@ -416,7 +432,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // We should have bubbles & their notifs should not be suppressed @@ -452,8 +468,8 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testRemoveLastExpandedCollapses() { // Mark it as a bubble and add it explicitly - mEntryListener.onNotificationAdded(mRow.getEntry()); - mEntryListener.onNotificationAdded(mRow2.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.updateBubble(mRow2.getEntry()); verify(mBubbleStateChangeListener).onHasBubblesChanged(true); @@ -471,7 +487,7 @@ public class BubbleControllerTest extends SysuiTestCase { mRow2.getEntry())); // Dismiss currently expanded - mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(), + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); @@ -480,7 +496,7 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one - mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(), + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -496,7 +512,7 @@ public class BubbleControllerTest extends SysuiTestCase { Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); // Add the auto expand bubble - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Expansion shouldn't change @@ -514,7 +530,7 @@ public class BubbleControllerTest extends SysuiTestCase { Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); // Add the auto expand bubble - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Expansion should change @@ -532,7 +548,7 @@ public class BubbleControllerTest extends SysuiTestCase { Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); // Add the suppress notif bubble - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Notif should be suppressed because we were foreground @@ -576,19 +592,19 @@ public class BubbleControllerTest extends SysuiTestCase { public void testExpandStackAndSelectBubble_removedFirst() { final String key = mRow.getEntry().getKey(); - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); // Simulate notification cancellation. mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), REASON_APP_CANCEL); + mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL); mBubbleController.expandStackAndSelectBubble(key); } @Test public void testMarkNewNotificationAsShowInShade() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -598,7 +614,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testAddNotif_notBubble() { - mEntryListener.onNotificationAdded(mNonBubbleNotifRow.getEntry()); + mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry()); mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry()); verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean()); @@ -608,7 +624,7 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); - mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED); verify(mDeleteIntent, never()).send(); } @@ -616,7 +632,7 @@ public class BubbleControllerTest extends SysuiTestCase { public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); verify(mDeleteIntent, times(1)).send(); } @@ -643,22 +659,33 @@ public class BubbleControllerTest extends SysuiTestCase { @Test public void testRemoveBubble_succeeds_appCancel() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), REASON_APP_CANCEL); + mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL); // Cancels always remove so no need to intercept assertFalse(intercepted); + } + + @Test + public void testRemoveBubble_entryListenerRemove() { + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + + // Removes the notification + mEntryListener.onEntryRemoved(mRow.getEntry(), null, false); assertFalse(mBubbleController.hasBubbles()); } @Test - public void removeBubble_fails_clearAll() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + public void removeBubble_clearAllIntercepted() { + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -666,22 +693,18 @@ public class BubbleControllerTest extends SysuiTestCase { mRow.getEntry())); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), REASON_CANCEL_ALL); + mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL_ALL); // Intercept! assertTrue(intercepted); // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); - - verify(mNotificationEntryManager, never()).performRemoveNotification( - any(), anyInt()); - assertTrue(mBubbleController.hasBubbles()); } @Test - public void removeBubble_fails_userDismissNotif() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + public void removeBubble_userDismissNotifIntercepted() { + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -689,22 +712,18 @@ public class BubbleControllerTest extends SysuiTestCase { mRow.getEntry())); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), REASON_CANCEL); + mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL); // Intercept! assertTrue(intercepted); // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); - - verify(mNotificationEntryManager, never()).performRemoveNotification( - any(), anyInt()); - assertTrue(mBubbleController.hasBubbles()); } @Test public void removeBubble_succeeds_userDismissBubble_userDimissNotif() { - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); @@ -713,12 +732,12 @@ public class BubbleControllerTest extends SysuiTestCase { // Dismiss the bubble mBubbleController.removeBubble( - mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); assertFalse(mBubbleController.hasBubbles()); // Dismiss the notification boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( - mRow.getEntry().getKey(), REASON_CANCEL); + mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL); // It's no longer a bubble so we shouldn't intercept assertFalse(intercepted); @@ -730,13 +749,14 @@ public class BubbleControllerTest extends SysuiTestCase { mock(BubbleController.NotificationSuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); - mRemoveInterceptor.onNotificationRemoveRequested(mRow.getEntry().getKey(), REASON_CANCEL); + mRemoveInterceptor.onNotificationRemoveRequested( + mRow.getEntry().getKey(), mRow.getEntry(), REASON_CANCEL); // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -753,7 +773,7 @@ public class BubbleControllerTest extends SysuiTestCase { mock(BubbleController.NotificationSuppressionChangedListener.class); mBubbleData.setSuppressionChangedListener(listener); - mEntryListener.onNotificationAdded(mRow.getEntry()); + mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( @@ -770,6 +790,74 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); } + @Test + public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN the summary and bubbled child are suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // GIVEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // WHEN the summary is cancelled by the app + mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, true); + + // THEN the summary and its children are removed from bubble data + assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + assertFalse(mBubbleData.isSummarySuppressed( + groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren() + throws Exception { + // GIVEN a group summary with two (non-bubble) children and one bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN only the NON-bubble children are dismissed + List<ExpandableNotificationRow> childrenRows = groupSummary.getNotificationChildren(); + verify(mNotificationEntryManager, times(1)).performRemoveNotification( + childrenRows.get(0).getEntry().getSbn(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotificationEntryManager, times(1)).performRemoveNotification( + childrenRows.get(1).getEntry().getSbn(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotificationEntryManager, never()).performRemoveNotification( + eq(groupedBubble.getEntry().getSbn()), anyInt()); + + // THEN the bubble child is suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + + // THEN the summary is removed from GroupManager + verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry()); + } + static class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, @@ -783,12 +871,14 @@ public class BubbleControllerTest extends SysuiTestCase { NotificationLockscreenUserManager lockscreenUserManager, NotificationGroupManager groupManager, NotificationEntryManager entryManager, - RemoteInputUriController remoteInputUriController) { + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, - remoteInputUriController); + notifPipeline, featureFlags, dumpController); setInflateSynchronously(true); } } @@ -805,7 +895,7 @@ public class BubbleControllerTest extends SysuiTestCase { } /** - * Sets the bubble metadata flags for this entry. These flags are normally set by + * Sets the bubble metadata flags for this entry. These ]flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not * go through that path so we set them explicitly when testing. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index c9f5b40e8f9f..f40fc94763ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -39,10 +39,10 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleData.TimeSource; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.google.common.collect.ImmutableList; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java new file mode 100644 index 000000000000..72405fc519fa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -0,0 +1,842 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles; + +import static android.app.Notification.FLAG_BUBBLE; +import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.IActivityManager; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.content.res.Resources; +import android.hardware.face.FaceManager; +import android.service.notification.ZenModeConfig; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.DumpController; +import com.android.systemui.SystemUIFactory; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.SuperStatusBarViewFactory; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationFilter; +import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; +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 com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.InjectionInflationController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +/** + * Tests the NotifPipeline setup with BubbleController. + * The NotificationEntryManager setup with BubbleController is tested in + * {@link BubbleControllerTest}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { + @Mock + private NotificationEntryManager mNotificationEntryManager; + @Mock + private NotificationGroupManager mNotificationGroupManager; + @Mock + private BubbleController.NotifCallback mNotifCallback; + @Mock + private WindowManager mWindowManager; + @Mock + private IActivityManager mActivityManager; + @Mock + private DozeParameters mDozeParameters; + @Mock + private ConfigurationController mConfigurationController; + @Mock + private ZenModeController mZenModeController; + @Mock + private ZenModeConfig mZenModeConfig; + @Mock + private FaceManager mFaceManager; + @Mock + private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock + private SysuiStatusBarStateController mStatusBarStateController; + @Mock + private KeyguardBypassController mKeyguardBypassController; + + @Captor + private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor; + + private TestableBubbleController mBubbleController; + private NotificationShadeWindowController mNotificationShadeWindowController; + private NotifCollectionListener mEntryListener; + + private NotificationTestHelper mNotificationTestHelper; + private ExpandableNotificationRow mRow; + private ExpandableNotificationRow mRow2; + private ExpandableNotificationRow mNonBubbleNotifRow; + + @Mock + private BubbleController.BubbleStateChangeListener mBubbleStateChangeListener; + @Mock + private BubbleController.BubbleExpandListener mBubbleExpandListener; + @Mock + private PendingIntent mDeleteIntent; + @Mock + private SysuiColorExtractor mColorExtractor; + @Mock + ColorExtractor.GradientColors mGradientColors; + @Mock + private Resources mResources; + @Mock + private ShadeController mShadeController; + @Mock + private NotificationRowComponent mNotificationRowComponent; + @Mock + private NotifPipeline mNotifPipeline; + @Mock + private FeatureFlags mFeatureFlagsNewPipeline; + @Mock + private DumpController mDumpController; + + private SuperStatusBarViewFactory mSuperStatusBarViewFactory; + private BubbleData mBubbleData; + + private TestableLooper mTestableLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + + mContext.addMockSystemService(FaceManager.class, mFaceManager); + when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); + + mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext, + new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()), + new NotificationRowComponent.Builder() { + @Override + public NotificationRowComponent.Builder activatableNotificationView( + ActivatableNotificationView view) { + return this; + } + + @Override + public NotificationRowComponent build() { + return mNotificationRowComponent; + } + }); + + // Bubbles get added to status bar window view + mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, + mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, + mConfigurationController, mKeyguardBypassController, mColorExtractor, + mSuperStatusBarViewFactory); + mNotificationShadeWindowController.attach(); + + // Need notifications for bubbles + mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mRow = mNotificationTestHelper.createBubble(mDeleteIntent); + mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); + mNonBubbleNotifRow = mNotificationTestHelper.createRow(); + + mZenModeConfig.suppressedVisualEffects = 0; + when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); + + TestableNotificationInterruptionStateProvider interruptionStateProvider = + new TestableNotificationInterruptionStateProvider(mContext, + mock(NotificationFilter.class), + mock(StatusBarStateController.class), + mock(BatteryController.class)); + interruptionStateProvider.setUpWithPresenter( + mock(NotificationPresenter.class), + mock(HeadsUpManager.class), + mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); + mBubbleData = new BubbleData(mContext); + when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true); + mBubbleController = new TestableBubbleController(mContext, + mNotificationShadeWindowController, + mStatusBarStateController, + mShadeController, + mBubbleData, + mConfigurationController, + interruptionStateProvider, + mZenModeController, + mLockscreenUserManager, + mNotificationGroupManager, + mNotificationEntryManager, + mNotifPipeline, + mFeatureFlagsNewPipeline, + mDumpController); + mBubbleController.addNotifCallback(mNotifCallback); + mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); + mBubbleController.setExpandListener(mBubbleExpandListener); + + // Get a reference to the BubbleController's entry listener + verify(mNotifPipeline, atLeastOnce()) + .addCollectionListener(mNotifListenerCaptor.capture()); + mEntryListener = mNotifListenerCaptor.getValue(); + } + + @Test + public void testAddBubble() { + mBubbleController.updateBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + + verify(mBubbleStateChangeListener).onHasBubblesChanged(true); + } + + @Test + public void testHasBubbles() { + assertFalse(mBubbleController.hasBubbles()); + mBubbleController.updateBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + } + + @Test + public void testRemoveBubble() { + mBubbleController.updateBubble(mRow.getEntry()); + assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + assertTrue(mBubbleController.hasBubbles()); + verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(true); + + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + assertFalse(mNotificationShadeWindowController.getBubblesShowing()); + assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(false); + } + + @Test + public void testRemoveBubble_withDismissedNotif() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + + // Make it look like dismissed notif + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setSuppressNotification(true); + + // Now remove the bubble + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + + // Since the notif is dismissed, once the bubble is removed, removeNotification gets + // called to really remove the notif + verify(mNotifCallback, times(1)).removeNotification(eq(mRow.getEntry()), anyInt()); + assertFalse(mBubbleController.hasBubbles()); + } + + @Test + public void testDismissStack() { + mBubbleController.updateBubble(mRow.getEntry()); + verify(mNotifCallback, times(1)).invalidateNotifications(anyString()); + assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + mBubbleController.updateBubble(mRow2.getEntry()); + verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); + assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey())); + assertTrue(mBubbleController.hasBubbles()); + + mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + assertFalse(mNotificationShadeWindowController.getBubblesShowing()); + verify(mNotifCallback, times(3)).invalidateNotifications(anyString()); + assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey())); + } + + @Test + public void testExpandCollapseStack() { + assertFalse(mBubbleController.isStackExpanded()); + + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + assertFalse(mNotificationShadeWindowController.getBubbleExpanded()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + assertTrue(mNotificationShadeWindowController.getBubbleExpanded()); + + // Make sure the notif is suppressed + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + + // Collapse + mBubbleController.collapseStack(); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + assertFalse(mBubbleController.isStackExpanded()); + assertFalse(mNotificationShadeWindowController.getBubbleExpanded()); + } + + @Test + public void testCollapseAfterChangingExpandedBubble() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow2.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow2.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow2.getEntry())); + + // Expand + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + + // Last added is the one that is expanded + assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry())); + + // Switch which bubble is expanded + mBubbleController.selectBubble(mRow.getEntry().getKey()); + mBubbleData.setExpanded(true); + assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // collapse for previous bubble + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + // expand for selected bubble + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Collapse + mBubbleController.collapseStack(); + assertFalse(mBubbleController.isStackExpanded()); + } + + @Test + public void testExpansionRemovesShowInShadeAndDot() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Expand + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Notif is suppressed after expansion + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Notif shouldn't show dot after expansion + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + } + + @Test + public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // We should have bubbles & their notifs should not be suppressed + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Expand + mBubbleController.expandStack(); + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Notif is suppressed after expansion + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Notif shouldn't show dot after expansion + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Send update + mEntryListener.onEntryUpdated(mRow.getEntry()); + + // Nothing should have changed + // Notif is suppressed after expansion + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Notif shouldn't show dot after expansion + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + } + + @Test + public void testRemoveLastExpandedCollapses() { + // Mark it as a bubble and add it explicitly + mEntryListener.onEntryAdded(mRow.getEntry()); + mEntryListener.onEntryAdded(mRow2.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow2.getEntry()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(true); + + // Expand + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleController.expandStack(); + + assertTrue(mBubbleController.isStackExpanded()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + + // Last added is the one that is expanded + assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry()); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow2.getEntry())); + + // Dismiss currently expanded + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), + BubbleController.DISMISS_USER_GESTURE); + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + + // Make sure first bubble is selected + assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); + verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + + // Dismiss that one + mBubbleController.removeBubble(stackView.getExpandedBubble().getEntry(), + BubbleController.DISMISS_USER_GESTURE); + + // Make sure state changes and collapse happens + verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey()); + verify(mBubbleStateChangeListener).onHasBubblesChanged(false); + assertFalse(mBubbleController.hasBubbles()); + } + + @Test + public void testAutoExpand_fails_noFlag() { + assertFalse(mBubbleController.isStackExpanded()); + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */); + + // Add the auto expand bubble + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // Expansion shouldn't change + verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, + mRow.getEntry().getKey()); + assertFalse(mBubbleController.isStackExpanded()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testAutoExpand_succeeds_withFlag() { + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */); + + // Add the auto expand bubble + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // Expansion should change + verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, + mRow.getEntry().getKey()); + assertTrue(mBubbleController.isStackExpanded()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testSuppressNotif_onInitialNotif() { + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); + + // Add the suppress notif bubble + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + // Notif should be suppressed because we were foreground + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Dot + flyout is hidden because notif is suppressed + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testSuppressNotif_onUpdateNotif() { + mBubbleController.updateBubble(mRow.getEntry()); + + // Should not be suppressed + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Should show dot + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + + // Update to suppress notif + setMetadataFlags(mRow.getEntry(), + Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */); + mBubbleController.updateBubble(mRow.getEntry()); + + // Notif should be suppressed + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + // Dot + flyout is hidden because notif is suppressed + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout()); + + // # of bubbles should change + verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); + } + + @Test + public void testMarkNewNotificationAsShowInShade() { + mEntryListener.onEntryAdded(mRow.getEntry()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mTestableLooper.processAllMessages(); + assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); + } + + @Test + public void testAddNotif_notBubble() { + mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry()); + mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry()); + + verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean()); + assertThat(mBubbleController.hasBubbles()).isFalse(); + } + + @Test + public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED); + verify(mDeleteIntent, never()).send(); + } + + @Test + public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.removeBubble( + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + verify(mDeleteIntent, times(1)).send(); + } + + @Test + public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow2.getEntry()); + mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + verify(mDeleteIntent, times(2)).send(); + } + + @Test + public void testRemoveBubble_noLongerBubbleAfterUpdate() + throws PendingIntent.CanceledException { + mBubbleController.updateBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + + mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; + mEntryListener.onEntryUpdated(mRow.getEntry()); + + assertFalse(mBubbleController.hasBubbles()); + verify(mDeleteIntent, never()).send(); + } + + @Test + public void testRemoveBubble_entryListenerRemove() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + + // Removes the notification + mEntryListener.onEntryRemoved(mRow.getEntry(), 0); + assertFalse(mBubbleController.hasBubbles()); + } + + @Test + public void removeBubble_intercepted() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry()); + + // Intercept! + assertTrue(intercepted); + // Should update show in shade state + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); + } + + @Test + public void removeBubble_succeeds_userDismissBubble_userDimissNotif() { + mEntryListener.onEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // Dismiss the bubble + mBubbleController.removeBubble( + mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE); + assertFalse(mBubbleController.hasBubbles()); + + // Dismiss the notification + boolean intercepted = mBubbleController.handleDismissalInterception(mRow.getEntry()); + + // It's no longer a bubble so we shouldn't intercept + assertFalse(intercepted); + } + + @Test + public void testNotifyShadeSuppressionChange_notificationDismiss() { + BubbleController.NotificationSuppressionChangedListener listener = + mock(BubbleController.NotificationSuppressionChangedListener.class); + mBubbleData.setSuppressionChangedListener(listener); + + mEntryListener.onEntryAdded(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mBubbleController.handleDismissalInterception(mRow.getEntry()); + + // Should update show in shade state + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // Should notify delegate that shade state changed + verify(listener).onBubbleNotificationSuppressionChange( + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + } + + @Test + public void testNotifyShadeSuppressionChange_bubbleExpanded() { + BubbleController.NotificationSuppressionChangedListener listener = + mock(BubbleController.NotificationSuppressionChangedListener.class); + mBubbleData.setSuppressionChangedListener(listener); + + mEntryListener.onEntryAdded(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mBubbleData.setExpanded(true); + + // Once a bubble is expanded the notif is suppressed + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + // Should notify delegate that shade state changed + verify(listener).onBubbleNotificationSuppressionChange( + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey())); + } + + @Test + public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN the summary and bubbled child are suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception { + // GIVEN a group summary with a bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + + // GIVEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // WHEN the summary is cancelled by the app + mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0); + + // THEN the summary and its children are removed from bubble data + assertFalse(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + assertFalse(mBubbleData.isSummarySuppressed( + groupSummary.getEntry().getSbn().getGroupKey())); + } + + @Test + public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren() + throws Exception { + // GIVEN a group summary with two (non-bubble) children and one bubble child + ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); + ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + mEntryListener.onEntryAdded(groupedBubble.getEntry()); + groupSummary.addChildNotification(groupedBubble); + + // WHEN the summary is dismissed + mBubbleController.handleDismissalInterception(groupSummary.getEntry()); + + // THEN only the NON-bubble children are dismissed + List<ExpandableNotificationRow> childrenRows = groupSummary.getNotificationChildren(); + verify(mNotifCallback, times(1)).removeNotification( + childrenRows.get(0).getEntry(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotifCallback, times(1)).removeNotification( + childrenRows.get(1).getEntry(), REASON_GROUP_SUMMARY_CANCELED); + verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()), anyInt()); + + // THEN the bubble child still exists as a bubble and is suppressed from the shade + assertTrue(mBubbleData.hasBubbleWithKey(groupedBubble.getEntry().getKey())); + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupedBubble.getEntry())); + + // THEN the summary is also suppressed from the shade + assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( + groupSummary.getEntry())); + } + + static class TestableBubbleController extends BubbleController { + // Let's assume surfaces can be synchronized immediately. + TestableBubbleController(Context context, + NotificationShadeWindowController notificationShadeWindowController, + StatusBarStateController statusBarStateController, + ShadeController shadeController, + BubbleData data, + ConfigurationController configurationController, + NotificationInterruptionStateProvider interruptionStateProvider, + ZenModeController zenModeController, + NotificationLockscreenUserManager lockscreenUserManager, + NotificationGroupManager groupManager, + NotificationEntryManager entryManager, + NotifPipeline notifPipeline, + FeatureFlags featureFlags, + DumpController dumpController) { + super(context, + notificationShadeWindowController, statusBarStateController, shadeController, + data, Runnable::run, configurationController, interruptionStateProvider, + zenModeController, lockscreenUserManager, groupManager, entryManager, + notifPipeline, featureFlags, dumpController); + setInflateSynchronously(true); + } + } + + static class TestableNotificationInterruptionStateProvider extends + NotificationInterruptionStateProvider { + + TestableNotificationInterruptionStateProvider(Context context, + NotificationFilter filter, StatusBarStateController controller, + BatteryController batteryController) { + super(context, filter, controller, batteryController); + mUseHeadsUp = true; + } + } + + /** + * Sets the bubble metadata flags for this entry. These flags are normally set by + * NotificationManagerService when the notification is sent, however, these tests do not + * go through that path so we set them explicitly when testing. + */ + private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) { + Notification.BubbleMetadata bubbleMetadata = + entry.getSbn().getNotification().getBubbleMetadata(); + int flags = bubbleMetadata.getFlags(); + if (enableFlag) { + flags |= flag; + } else { + flags &= ~flag; + } + bubbleMetadata.setFlags(flags); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt index 7c8c7c8f7be6..89c1636e953f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.controls.controller import android.content.ComponentName import android.content.Context import android.os.Binder +import android.os.UserHandle import android.service.controls.Control import android.service.controls.DeviceTypes import android.testing.AndroidTestingRunner @@ -38,6 +39,7 @@ import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -55,6 +57,9 @@ class ControlsBindingControllerTest : SysuiTestCase() { @Mock private lateinit var mockControlsController: ControlsController + private val user = UserHandle.of(mContext.userId) + private val otherUser = UserHandle.of(user.identifier + 1) + private val executor = FakeExecutor(FakeSystemClock()) private lateinit var controller: ControlsBindingController private val providers = TestableControlsBindingControllerImpl.providers @@ -75,6 +80,11 @@ class ControlsBindingControllerTest : SysuiTestCase() { } @Test + fun testStartOnUser() { + assertEquals(user.identifier, controller.currentUserId) + } + + @Test fun testBindAndLoad() { val callback: (List<Control>) -> Unit = {} controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback) @@ -93,7 +103,7 @@ class ControlsBindingControllerTest : SysuiTestCase() { assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2), providers.map { it.componentName }.toSet()) providers.forEach { - verify(it).bindPermanently() + verify(it).bindService() } } @@ -145,6 +155,41 @@ class ControlsBindingControllerTest : SysuiTestCase() { verify(it).unsubscribe() } } + + @Test + fun testCurrentUserId() { + controller.changeUser(otherUser) + assertEquals(otherUser.identifier, controller.currentUserId) + } + + @Test + fun testChangeUsers_providersHaveCorrectUser() { + controller.bindServices(listOf(TEST_COMPONENT_NAME_1)) + controller.changeUser(otherUser) + controller.bindServices(listOf(TEST_COMPONENT_NAME_2)) + + val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 } + assertEquals(user, provider1.user) + val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 } + assertEquals(otherUser, provider2.user) + } + + @Test + fun testChangeUsers_providersUnbound() { + controller.bindServices(listOf(TEST_COMPONENT_NAME_1)) + controller.changeUser(otherUser) + + val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 } + verify(provider1).unbindService() + + controller.bindServices(listOf(TEST_COMPONENT_NAME_2)) + controller.changeUser(user) + + reset(provider1) + val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 } + verify(provider2).unbindService() + verify(provider1, never()).unbindService() + } } class TestableControlsBindingControllerImpl( @@ -157,13 +202,17 @@ class TestableControlsBindingControllerImpl( val providers = mutableSetOf<ControlsProviderLifecycleManager>() } + // Replaces the real provider with a mock and puts the mock in a visible set. + // The mock has the same componentName and user as the real one would have override fun createProviderManager(component: ComponentName): ControlsProviderLifecycleManager { + val realProvider = super.createProviderManager(component) val provider = mock(ControlsProviderLifecycleManager::class.java) val token = Binder() - `when`(provider.componentName).thenReturn(component) + `when`(provider.componentName).thenReturn(realProvider.componentName) `when`(provider.token).thenReturn(token) + `when`(provider.user).thenReturn(realProvider.user) providers.add(provider) return provider } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index be86a9c15e5f..751217f03fa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -17,7 +17,12 @@ package com.android.systemui.controls.controller import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.os.UserHandle import android.provider.Settings import android.service.controls.Control import android.service.controls.DeviceTypes @@ -26,21 +31,26 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.DumpController import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -61,18 +71,25 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var pendingIntent: PendingIntent @Mock private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock + private lateinit var listingController: ControlsListingController @Captor private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>> @Captor private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit> + @Captor + private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> private lateinit var delayableExecutor: FakeExecutor - private lateinit var controller: ControlsController + private lateinit var controller: ControlsControllerImpl companion object { fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() - fun <T : Any> safeEq(value: T): T = eq(value) ?: value + fun <T> eq(value: T): T = Mockito.eq(value) ?: value + fun <T> any(): T = Mockito.any<T>() private val TEST_COMPONENT = ComponentName("test.pkg", "test.class") private const val TEST_CONTROL_ID = "control1" @@ -89,24 +106,39 @@ class ControlsControllerImplTest : SysuiTestCase() { TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2) } + private val user = mContext.userId + private val otherUser = user + 1 + @Before fun setUp() { MockitoAnnotations.initMocks(this) Settings.Secure.putInt(mContext.contentResolver, ControlsControllerImpl.CONTROLS_AVAILABLE, 1) + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser) delayableExecutor = FakeExecutor(FakeSystemClock()) + val wrapper = object : ContextWrapper(mContext) { + override fun createContextAsUser(user: UserHandle, flags: Int): Context { + return baseContext + } + } + controller = ControlsControllerImpl( - mContext, + wrapper, delayableExecutor, uiController, bindingController, + listingController, + broadcastDispatcher, Optional.of(persistenceWrapper), dumpController ) assertTrue(controller.available) + verify(broadcastDispatcher).registerReceiver( + capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL)) } private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder { @@ -115,6 +147,11 @@ class ControlsControllerImplTest : SysuiTestCase() { } @Test + fun testStartOnUser() { + assertEquals(user, controller.currentUserId) + } + + @Test fun testStartWithoutFavorites() { assertTrue(controller.getFavoriteControls().isEmpty()) } @@ -127,6 +164,8 @@ class ControlsControllerImplTest : SysuiTestCase() { delayableExecutor, uiController, bindingController, + listingController, + broadcastDispatcher, Optional.of(persistenceWrapper), dumpController ) @@ -187,10 +226,10 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } reset(persistenceWrapper) - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control)) @@ -255,14 +294,16 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false val control = builderFromInfo(TEST_CONTROL_INFO).build() - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(ControlStatus(control, false), controlStatus) + + assertTrue(favorites.isEmpty()) } - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control)) @@ -277,17 +318,20 @@ class ControlsControllerImplTest : SysuiTestCase() { val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build() controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(2, it.size) - val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID } + assertEquals(2, controls.size) + val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID } assertEquals(ControlStatus(control, true), controlStatus) - val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 } + val controlStatus2 = controls.first { it.control.controlId == TEST_CONTROL_ID_2 } assertEquals(ControlStatus(control2, false), controlStatus2) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control, control2)) @@ -300,16 +344,19 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId) assertTrue(controlStatus.favorite) assertTrue(controlStatus.removed) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(emptyList()) @@ -323,9 +370,9 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } - verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT), + verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.invoke(listOf(control)) @@ -358,4 +405,168 @@ class ControlsControllerImplTest : SysuiTestCase() { controller.clearFavorites() assertTrue(controller.getFavoriteControls().isEmpty()) } + + @Test + fun testSwitchUsers() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + reset(persistenceWrapper) + val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, otherUser) + } + val pendingResult = mock(BroadcastReceiver.PendingResult::class.java) + `when`(pendingResult.sendingUserId).thenReturn(otherUser) + broadcastReceiverCaptor.value.pendingResult = pendingResult + + broadcastReceiverCaptor.value.onReceive(mContext, intent) + + verify(persistenceWrapper).changeFile(any()) + verify(persistenceWrapper).readFavorites() + verify(bindingController).changeUser(UserHandle.of(otherUser)) + verify(listingController).changeUser(UserHandle.of(otherUser)) + assertTrue(controller.getFavoriteControls().isEmpty()) + assertEquals(otherUser, controller.currentUserId) + assertTrue(controller.available) + } + + @Test + fun testDisableFeature_notAvailable() { + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, 0) + assertFalse(controller.available) + } + + @Test + fun testDisableFeature_clearFavorites() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertFalse(controller.getFavoriteControls().isEmpty()) + + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, user) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, user) + assertTrue(controller.getFavoriteControls().isEmpty()) + } + + @Test + fun testDisableFeature_noChangeForNotCurrentUser() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser) + controller.settingObserver.onChange(false, ControlsControllerImpl.URI, otherUser) + + assertTrue(controller.available) + assertFalse(controller.getFavoriteControls().isEmpty()) + } + + @Test + fun testCorrectUserSettingOnUserChange() { + Settings.Secure.putIntForUser(mContext.contentResolver, + ControlsControllerImpl.CONTROLS_AVAILABLE, 0, otherUser) + + val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, otherUser) + } + val pendingResult = mock(BroadcastReceiver.PendingResult::class.java) + `when`(pendingResult.sendingUserId).thenReturn(otherUser) + broadcastReceiverCaptor.value.pendingResult = pendingResult + + broadcastReceiverCaptor.value.onReceive(mContext, intent) + + assertFalse(controller.available) + } + + @Test + fun testCountFavoritesForComponent_singleComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(0, controller.countFavoritesForComponent(TEST_COMPONENT_2)) + } + + @Test + fun testCountFavoritesForComponent_multipleComponents() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2)) + } + + @Test + fun testGetFavoritesForComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testGetFavoritesForComponent_otherComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + assertTrue(controller.getFavoritesForComponent(TEST_COMPONENT).isEmpty()) + } + + @Test + fun testGetFavoritesForComponent_multipleInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(controlInfo, true) + + assertEquals(listOf(TEST_CONTROL_INFO, controlInfo), + controller.getFavoritesForComponent(TEST_COMPONENT)) + + controller.clearFavorites() + + controller.changeFavoriteStatus(controlInfo, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + assertEquals(listOf(controlInfo, TEST_CONTROL_INFO), + controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_noFavorites() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_differentComponentsAreFilteredOut() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, + listOf(TEST_CONTROL_INFO, TEST_CONTROL_INFO_2)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_oldFavoritesRemoved() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + assertNotEquals(TEST_CONTROL_INFO, controlInfo) + + controller.changeFavoriteStatus(controlInfo, true) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_favoritesInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + val listOrder1 = listOf(TEST_CONTROL_INFO, controlInfo) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder1) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder1, controller.getFavoritesForComponent(TEST_COMPONENT)) + + val listOrder2 = listOf(controlInfo, TEST_CONTROL_INFO) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder2) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt index 4fc1cca76be6..40566dc39c9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.controller import android.content.ComponentName +import android.os.UserHandle import android.service.controls.Control import android.service.controls.IControlsActionCallback import android.service.controls.IControlsLoadCallback @@ -86,6 +87,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { loadCallback, actionCallback, subscriber, + UserHandle.of(0), componentName ) } @@ -97,13 +99,13 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { @Test fun testBindService() { - manager.bindPermanently() + manager.bindService() assertTrue(mContext.isBound(componentName)) } @Test fun testUnbindService() { - manager.bindPermanently() + manager.bindService() manager.unbindService() assertFalse(mContext.isBound(componentName)) } @@ -123,7 +125,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { fun testMaybeUnbind_bindingAndCallback() { manager.maybeBindAndLoad {} - manager.maybeUnbindAndRemoveCallback() + manager.unbindService() assertFalse(mContext.isBound(componentName)) assertNull(manager.lastLoadCallback) } @@ -148,4 +150,4 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() { eq(actionCallback)) assertEquals(action, wrapperCaptor.getValue().getWrappedAction()) } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index f09aab97a219..85e937e40acd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -17,12 +17,15 @@ package com.android.systemui.controls.management import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper import android.content.pm.ServiceInfo +import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.settingslib.applications.ServiceListing -import com.android.settingslib.widget.CandidateInfo import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.After @@ -69,13 +72,22 @@ class ControlsListingControllerImplTest : SysuiTestCase() { private var serviceListingCallbackCaptor = ArgumentCaptor.forClass(ServiceListing.Callback::class.java) + private val user = mContext.userId + private val otherUser = user + 1 + @Before fun setUp() { MockitoAnnotations.initMocks(this) `when`(serviceInfo.componentName).thenReturn(componentName) - controller = ControlsListingControllerImpl(mContext, executor, mockSL) + val wrapper = object : ContextWrapper(mContext) { + override fun createContextAsUser(user: UserHandle, flags: Int): Context { + return baseContext + } + } + + controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }) verify(mockSL).addCallback(capture(serviceListingCallbackCaptor)) } @@ -86,6 +98,11 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } @Test + fun testStartsOnUser() { + assertEquals(user, controller.currentUserId) + } + + @Test fun testNoServices_notListening() { assertTrue(controller.getCurrentServices().isEmpty()) } @@ -167,8 +184,9 @@ class ControlsListingControllerImplTest : SysuiTestCase() { controller.addCallback(mockCallbackOther) @Suppress("unchecked_cast") - val captor: ArgumentCaptor<List<CandidateInfo>> = - ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>> + val captor: ArgumentCaptor<List<ControlsServiceInfo>> = + ArgumentCaptor.forClass(List::class.java) + as ArgumentCaptor<List<ControlsServiceInfo>> executor.runAllReady() reset(mockCallback) @@ -185,4 +203,11 @@ class ControlsListingControllerImplTest : SysuiTestCase() { assertEquals(1, captor.value.size) assertEquals(componentName.flattenToString(), captor.value[0].key) } + + @Test + fun testChangeUser() { + controller.changeUser(UserHandle.of(otherUser)) + executor.runAllReady() + assertEquals(otherUser, controller.currentUserId) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt new file mode 100644 index 000000000000..9ffc29e0eb7e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.app.PendingIntent +import android.service.controls.Control +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlStatus +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +open class FavoriteModelTest : SysuiTestCase() { + + @Mock + lateinit var pendingIntent: PendingIntent + @Mock + lateinit var allAdapter: ControlAdapter + @Mock + lateinit var favoritesAdapter: ControlAdapter + + val idPrefix = "controlId" + val favoritesIndices = listOf(7, 3, 1, 9) + val favoritesList = favoritesIndices.map { "controlId$it" } + lateinit var controls: List<ControlStatus> + + lateinit var model: FavoriteModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // controlId0 --> zone = 0 + // controlId1 --> zone = 1, favorite + // controlId2 --> zone = 2 + // controlId3 --> zone = 0, favorite + // controlId4 --> zone = 1 + // controlId5 --> zone = 2 + // controlId6 --> zone = 0 + // controlId7 --> zone = 1, favorite + // controlId8 --> zone = 2 + // controlId9 --> zone = 0, favorite + controls = (0..9).map { + ControlStatus( + Control.StatelessBuilder("$idPrefix$it", pendingIntent) + .setZone((it % 3).toString()) + .build(), + it in favoritesIndices + ) + } + + model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter) + } +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FavoriteModelNonParametrizedTests : FavoriteModelTest() { + @Test + fun testAll() { + // Zones are sorted alphabetically + val expected = listOf( + ZoneNameWrapper("0"), + ControlWrapper(controls[0]), + ControlWrapper(controls[3]), + ControlWrapper(controls[6]), + ControlWrapper(controls[9]), + ZoneNameWrapper("1"), + ControlWrapper(controls[1]), + ControlWrapper(controls[4]), + ControlWrapper(controls[7]), + ZoneNameWrapper("2"), + ControlWrapper(controls[2]), + ControlWrapper(controls[5]), + ControlWrapper(controls[8]) + ) + assertEquals(expected, model.all) + } + + @Test + fun testFavoritesInOrder() { + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + } + + @Test + fun testChangeFavoriteStatus_addFavorite() { + val controlToAdd = 6 + model.changeFavoriteStatus("$idPrefix$controlToAdd", true) + + val pair = model.all.findControl(controlToAdd) + pair?.let { + assertTrue(it.second.favorite) + assertEquals(it.second, model.favorites.last().controlStatus) + verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_removeFavorite() { + val controlToRemove = 3 + model.changeFavoriteStatus("$idPrefix$controlToRemove", false) + + val pair = model.all.findControl(controlToRemove) + pair?.let { + assertFalse(it.second.favorite) + assertTrue(model.favorites.none { + it.controlStatus.control.controlId == "$idPrefix$controlToRemove" + }) + verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove)) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_sameStatus() { + model.changeFavoriteStatus("${idPrefix}7", true) + model.changeFavoriteStatus("${idPrefix}6", false) + + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } + + private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? { + val index = indexOfFirst { + it is ControlWrapper && + it.controlStatus.control.controlId == "$idPrefix$controlIndex" + } + return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus + } +} + +@SmallTest +@RunWith(Parameterized::class) +class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0} -> {1}") + fun data(): Collection<Array<Int>> { + return (0..3).flatMap { from -> + (0..3).map { to -> + arrayOf(from, to) + } + }.filterNot { it[0] == it[1] } + } + } + + @Test + fun testMoveItem() { + val originalFavorites = model.favorites.toList() + val originalFavoritesIds = + model.favorites.map { it.controlStatus.control.controlId }.toSet() + model.onMoveItem(from, to) + assertEquals(originalFavorites[from], model.favorites[to]) + // Check that we still have the same favorites + assertEquals(originalFavoritesIds, + model.favorites.map { it.controlStatus.control.controlId }.toSet()) + + verify(favoritesAdapter).notifyItemMoved(from, to) + + verifyNoMoreInteractions(allAdapter, favoritesAdapter) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index 752e145d79bd..00d333fb593b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -53,6 +53,7 @@ public class DozeConfigurationUtil { when(config.pickupGestureEnabled(anyInt())).thenReturn(false); when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true); when(config.alwaysOnEnabled(anyInt())).thenReturn(false); + when(config.dozeSuppressed(anyInt())).thenReturn(false); when(config.enabled(anyInt())).thenReturn(true); when(config.getWakeLockScreenDebounce()).thenReturn(0L); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index f4cf314aa8fd..63cbca9255a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -139,6 +139,65 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testInitialize_dozeSuppressed_alwaysOnDisabled_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_alwaysOnEnabled_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_afterDocked_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); + when(mDockManager.isDocked()).thenReturn(true); + when(mDockManager.isHidden()).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test + public void testInitialize_dozeSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + when(mDockManager.isHidden()).thenReturn(true); + + mMachine.requestState(INITIALIZED); + + verify(mPartMock).transitionTo(INITIALIZED, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testPulseDone_goesToDoze() { when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); mMachine.requestState(INITIALIZED); @@ -165,6 +224,20 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_dozeSuppressed_goesToSuppressed() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testPulseDone_afterDocked_goesToDockedAoD() { when(mDockManager.isDocked()).thenReturn(true); mMachine.requestState(INITIALIZED); @@ -178,6 +251,20 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testPulseDone_afterDockPaused_goesToDoze() { when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); @@ -193,6 +280,22 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_dozeSuppressed_afterDockPaused_goesToDoze() { + when(mConfigMock.dozeSuppressed(anyInt())).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mDockManager.isDocked()).thenReturn(true); + when(mDockManager.isHidden()).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); + mMachine.requestState(DOZE_PULSING); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock).transitionTo(DOZE_PULSE_DONE, DOZE); + assertEquals(DOZE, mMachine.getState()); + } + + @Test public void testFinished_staysFinished() { mMachine.requestState(INITIALIZED); mMachine.requestState(FINISH); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java new file mode 100644 index 000000000000..5bdca76d449c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressedHandlerTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.doze; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.hardware.display.AmbientDisplayConfiguration; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.doze.DozeSuppressedHandler.DozeSuppressedSettingObserver; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class DozeSuppressedHandlerTest extends SysuiTestCase { + @Mock private DozeMachine mMachine; + @Mock private DozeSuppressedSettingObserver mObserver; + private AmbientDisplayConfiguration mConfig; + private DozeSuppressedHandler mSuppressedHandler; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mConfig = DozeConfigurationUtil.createMockConfig(); + mSuppressedHandler = new DozeSuppressedHandler(mContext, mConfig, mMachine, mObserver); + } + + @Test + public void transitionTo_initialized_registersObserver() throws Exception { + mSuppressedHandler.transitionTo(DozeMachine.State.UNINITIALIZED, + DozeMachine.State.INITIALIZED); + + verify(mObserver).register(); + } + + @Test + public void transitionTo_finish_unregistersObserver() throws Exception { + mSuppressedHandler.transitionTo(DozeMachine.State.INITIALIZED, + DozeMachine.State.FINISH); + + verify(mObserver).unregister(); + } + + @Test + public void transitionTo_doze_doesNothing() throws Exception { + mSuppressedHandler.transitionTo(DozeMachine.State.INITIALIZED, + DozeMachine.State.DOZE); + + verify(mObserver, never()).register(); + verify(mObserver, never()).unregister(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java index a5722e211f98..4b47093bb951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java @@ -16,52 +16,116 @@ package com.android.systemui.glwallpaper; -import static org.junit.Assert.*; -import static org.mockito.Mockito.RETURNS_DEFAULTS; -import static org.mockito.Mockito.mock; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.graphics.PixelFormat; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; +import android.view.Surface; +import android.view.SurfaceControl; import android.view.SurfaceHolder; +import android.view.SurfaceSession; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; - +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper public class EglHelperTest extends SysuiTestCase { - @Mock + @Spy private EglHelper mEglHelper; + @Mock private SurfaceHolder mSurfaceHolder; @Before public void setUp() throws Exception { - mEglHelper = mock(EglHelper.class, RETURNS_DEFAULTS); - mSurfaceHolder = mock(SurfaceHolder.class, RETURNS_DEFAULTS); + MockitoAnnotations.initMocks(this); + prepareSurface(); + } + + @After + public void tearDown() { + mSurfaceHolder.getSurface().destroy(); + mSurfaceHolder = null; + } + + private void prepareSurface() { + final SurfaceSession session = new SurfaceSession(); + final SurfaceControl control = new SurfaceControl.Builder(session) + .setName("Test") + .setBufferSize(100, 100) + .setFormat(PixelFormat.RGB_888) + .build(); + final Surface surface = new Surface(); + surface.copyFrom(control); + when(mSurfaceHolder.getSurface()).thenReturn(surface); + assertThat(mSurfaceHolder.getSurface()).isNotNull(); + assertThat(mSurfaceHolder.getSurface().isValid()).isTrue(); } @Test public void testInit_finish() { mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */); + assertThat(mEglHelper.hasEglDisplay()).isTrue(); + assertThat(mEglHelper.hasEglContext()).isTrue(); + assertThat(mEglHelper.hasEglSurface()).isTrue(); + verify(mEglHelper).askCreatingEglWindowSurface( + any(SurfaceHolder.class), eq(null), anyInt()); + + mEglHelper.finish(); + assertThat(mEglHelper.hasEglSurface()).isFalse(); + assertThat(mEglHelper.hasEglContext()).isFalse(); + assertThat(mEglHelper.hasEglDisplay()).isFalse(); + } + + @Test + public void testInit_finish_wide_gamut() { + // In EglHelper, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490; + doReturn(0x3490).when(mEglHelper).getWcgCapability(); + // In EglHelper, KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace"; + doReturn(true).when(mEglHelper).checkExtensionCapability("EGL_KHR_gl_colorspace"); + ArgumentCaptor<int[]> ac = ArgumentCaptor.forClass(int[].class); + // {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT, EGL_NONE} + final int[] expectedArgument = new int[] {0x309D, 0x3490, 0x3038}; + + mEglHelper.init(mSurfaceHolder, true /* wideColorGamut */); + verify(mEglHelper) + .askCreatingEglWindowSurface(any(SurfaceHolder.class), ac.capture(), anyInt()); + assertThat(ac.getValue()).isNotNull(); + assertThat(ac.getValue()).isEqualTo(expectedArgument); mEglHelper.finish(); } @Test public void testFinish_shouldNotCrash() { - assertFalse(mEglHelper.hasEglDisplay()); - assertFalse(mEglHelper.hasEglSurface()); - assertFalse(mEglHelper.hasEglContext()); + mEglHelper.terminateEglDisplay(); + assertThat(mEglHelper.hasEglDisplay()).isFalse(); + assertThat(mEglHelper.hasEglSurface()).isFalse(); + assertThat(mEglHelper.hasEglContext()).isFalse(); mEglHelper.finish(); + verify(mEglHelper, never()).destroyEglContext(); + verify(mEglHelper, never()).destroyEglSurface(); + verify(mEglHelper, atMost(1)).terminateEglDisplay(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java new file mode 100644 index 000000000000..d881fd51712d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.glwallpaper; + +import static com.android.systemui.glwallpaper.GLWallpaperRenderer.SurfaceProxy; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.app.WallpaperManager; +import android.app.WallpaperManager.ColorManagementProxy; +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class ImageWallpaperRendererTest extends SysuiTestCase { + + private WallpaperManager mWpmSpy; + private SurfaceProxy mSurfaceProxy; + + @Before + public void setUp() throws Exception { + final WallpaperManager wpm = mContext.getSystemService(WallpaperManager.class); + mWpmSpy = spy(wpm); + mContext.addMockSystemService(WallpaperManager.class, mWpmSpy); + + mSurfaceProxy = new SurfaceProxy() { + @Override + public void requestRender() { + // NO-op + } + + @Override + public void preRender() { + // No-op + } + + @Override + public void postRender() { + // No-op + } + }; + } + + @Test + public void testWcgContent() throws IOException { + final Bitmap srgbBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + final Bitmap p3Bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888, + false /* hasAlpha */, ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); + + final ColorManagementProxy proxy = new ColorManagementProxy(mContext); + final ColorManagementProxy cmProxySpy = spy(proxy); + final Set<ColorSpace> supportedWideGamuts = new HashSet<>(); + supportedWideGamuts.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); + + try { + doReturn(true).when(mWpmSpy).shouldEnableWideColorGamut(); + doReturn(cmProxySpy).when(mWpmSpy).getColorManagementProxy(); + doReturn(supportedWideGamuts).when(cmProxySpy).getSupportedColorSpaces(); + + mWpmSpy.setBitmap(p3Bitmap); + ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + assertThat(rendererP3.isWcgContent()).isTrue(); + + mWpmSpy.setBitmap(srgbBitmap); + ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + assertThat(renderer.isWcgContent()).isFalse(); + } finally { + srgbBitmap.recycle(); + p3Bitmap.recycle(); + } + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java deleted file mode 100644 index 4a90bb91ca37..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java +++ /dev/null @@ -1,69 +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.log; - -import static junit.framework.Assert.assertEquals; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import junit.framework.Assert; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class RichEventTest extends SysuiTestCase { - - private static final int TOTAL_EVENT_TYPES = 1; - - @Test - public void testCreateRichEvent_invalidType() { - try { - // indexing for events starts at 0, so TOTAL_EVENT_TYPES is an invalid type - new TestableRichEvent(Event.DEBUG, TOTAL_EVENT_TYPES, "msg"); - } catch (IllegalArgumentException e) { - // expected - return; - } - - Assert.fail("Expected an invalidArgumentException since the event type was invalid."); - } - - @Test - public void testCreateRichEvent() { - final int eventType = 0; - RichEvent e = new TestableRichEvent(Event.DEBUG, eventType, "msg"); - assertEquals(e.getType(), eventType); - } - - class TestableRichEvent extends RichEvent { - TestableRichEvent(int logLevel, int type, String reason) { - init(logLevel, type, reason); - } - - @Override - public String[] getEventLabels() { - return new String[]{"ACTION_NAME"}; - } - } - -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java deleted file mode 100644 index e7b317e882ef..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java +++ /dev/null @@ -1,111 +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.log; - -import static junit.framework.Assert.assertEquals; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.DumpController; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class SysuiLogTest extends SysuiTestCase { - private static final String TEST_ID = "TestLogger"; - private static final String TEST_MSG = "msg"; - private static final int MAX_LOGS = 5; - - @Mock - private DumpController mDumpController; - private SysuiLog<Event> mSysuiLog; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testLogDisabled_noLogsWritten() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, false); - assertEquals(null, mSysuiLog.mTimeline); - - mSysuiLog.log(createEvent(TEST_MSG)); - assertEquals(null, mSysuiLog.mTimeline); - } - - @Test - public void testLogEnabled_logWritten() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - assertEquals(0, mSysuiLog.mTimeline.size()); - - mSysuiLog.log(createEvent(TEST_MSG)); - assertEquals(1, mSysuiLog.mTimeline.size()); - } - - @Test - public void testMaxLogs() { - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - assertEquals(mSysuiLog.mTimeline.size(), 0); - - for (int i = 0; i < MAX_LOGS + 1; i++) { - mSysuiLog.log(createEvent(TEST_MSG + i)); - } - - assertEquals(MAX_LOGS, mSysuiLog.mTimeline.size()); - - // check the first message (msg0) was replaced with msg1: - assertEquals(TEST_MSG + "1", mSysuiLog.mTimeline.getFirst().getMessage()); - } - - @Test - public void testRecycleLogs() { - // GIVEN a SysuiLog with one log - mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true); - Event e = createEvent(TEST_MSG); // msg - mSysuiLog.log(e); // Logs: [msg] - - Event recycledEvent = null; - // WHEN we add MAX_LOGS after the first log - for (int i = 0; i < MAX_LOGS; i++) { - recycledEvent = mSysuiLog.log(createEvent(TEST_MSG + i)); - } - // Logs: [msg1, msg2, msg3, msg4] - - // THEN we see the recycledEvent is e - assertEquals(e, recycledEvent); - } - - private Event createEvent(String msg) { - return new Event().init(msg); - } - - public class TestSysuiLog extends SysuiLog<Event> { - protected TestSysuiLog(DumpController dumpController, String id, int maxLogs, - boolean enabled) { - super(dumpController, id, maxLogs, enabled, false); - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index 47933ba9fdaa..e58a3a6bf5d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -16,8 +16,11 @@ package com.android.systemui.qs; import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -447,6 +450,21 @@ public class QSSecurityFooterTest extends SysuiTestCase { view.findViewById(R.id.vpn_subtitle).getVisibility()); } + @Test + public void testNoClickWhenGone() { + QSTileHost mockHost = mock(QSTileHost.class); + mFooter.setHostEnvironment(mockHost); + mFooter.refreshState(); + + TestableLooper.get(this).processAllMessages(); + + assertFalse(mFooter.hasFooter()); + mFooter.onClick(mFooter.getView()); + + // Proxy for dialog being created + verify(mockHost, never()).collapsePanels(); + } + private CharSequence addLink(CharSequence description) { final SpannableStringBuilder message = new SpannableStringBuilder(); message.append(description); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index 4becd522ebd6..9fe2569177d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -88,8 +88,8 @@ class CustomTileTest : SysuiTestCase() { } @Test - fun testBooleanTileHasBooleanState() { - `when`(mTileServiceManager.isBooleanTile).thenReturn(true) + fun testToggleableTileHasBooleanState() { + `when`(mTileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(mTileHost, TILE_SPEC) assertTrue(customTile.state is QSTile.BooleanState) @@ -104,7 +104,7 @@ class CustomTileTest : SysuiTestCase() { @Test fun testValueUpdatedInBooleanTile() { - `when`(mTileServiceManager.isBooleanTile).thenReturn(true) + `when`(mTileServiceManager.isToggleableTile).thenReturn(true) customTile = CustomTile.create(mTileHost, TILE_SPEC) customTile.qsTile.icon = mock(Icon::class.java) `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 9e5e582bf5e7..42fd288d94ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -105,7 +105,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { defaultServiceInfo = new ServiceInfo(); defaultServiceInfo.metaData = new Bundle(); defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true); - defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_BOOLEAN_TILE, true); + defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_TOGGLEABLE_TILE, true); } when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt())) .thenReturn(defaultServiceInfo); @@ -244,7 +244,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } @Test - public void testBooleanTile() throws Exception { - assertTrue(mStateManager.isBooleanTile()); + public void testToggleableTile() throws Exception { + assertTrue(mStateManager.isToggleableTile()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/recents/model/TaskKeyLruCacheTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/recents/model/TaskKeyLruCacheTest.java deleted file mode 100644 index de6c87c7ff01..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/recents/model/TaskKeyLruCacheTest.java +++ /dev/null @@ -1,140 +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.shared.recents.model; - - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNull; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -@SmallTest -@RunWith(MockitoJUnitRunner.class) -public class TaskKeyLruCacheTest extends SysuiTestCase { - private static int sCacheSize = 3; - private static int sIdTask1 = 1; - private static int sIdTask2 = 2; - private static int sIdTask3 = 3; - private static int sIdUser1 = 1; - - TaskKeyLruCache.EvictionCallback mEvictionCallback; - - TaskKeyLruCache<Integer> mCache; - private Task.TaskKey mKey1; - private Task.TaskKey mKey2; - private Task.TaskKey mKey3; - - @Before - public void setup() { - mEvictionCallback = mock(TaskKeyLruCache.EvictionCallback.class); - mCache = new TaskKeyLruCache<>(sCacheSize, mEvictionCallback); - - mKey1 = new Task.TaskKey(sIdTask1, 0, null, null, sIdUser1, System.currentTimeMillis()); - mKey2 = new Task.TaskKey(sIdTask2, 0, null, null, sIdUser1, System.currentTimeMillis()); - mKey3 = new Task.TaskKey(sIdTask3, 0, null, null, sIdUser1, System.currentTimeMillis()); - } - - @Test - public void addSingleItem_get_success() { - mCache.put(mKey1, 1); - - assertEquals(1, (int) mCache.get(mKey1)); - } - - @Test - public void addSingleItem_getUninsertedItem_returnsNull() { - mCache.put(mKey1, 1); - - assertNull(mCache.get(mKey2)); - } - - @Test - public void emptyCache_get_returnsNull() { - assertNull(mCache.get(mKey1)); - } - - @Test - public void updateItem_get_returnsSecond() { - mCache.put(mKey1, 1); - mCache.put(mKey1, 2); - - assertEquals(2, (int) mCache.get(mKey1)); - assertEquals(1, mCache.mKeys.size()); - } - - @Test - public void fillCache_put_evictsOldest() { - mCache.put(mKey1, 1); - mCache.put(mKey2, 2); - mCache.put(mKey3, 3); - Task.TaskKey key4 = new Task.TaskKey(sIdTask3 + 1, 0, - null, null, sIdUser1, System.currentTimeMillis()); - mCache.put(key4, 4); - - assertNull(mCache.get(mKey1)); - assertEquals(3, mCache.mKeys.size()); - assertEquals(mKey2, mCache.mKeys.valueAt(0)); - verify(mEvictionCallback, times(1)).onEntryEvicted(mKey1); - } - - @Test - public void fillCache_remove_success() { - mCache.put(mKey1, 1); - mCache.put(mKey2, 2); - mCache.put(mKey3, 3); - - mCache.remove(mKey2); - - assertNull(mCache.get(mKey2)); - assertEquals(2, mCache.mKeys.size()); - verify(mEvictionCallback, times(0)).onEntryEvicted(mKey2); - } - - @Test - public void put_evictionCallback_notCalled() { - mCache.put(mKey1, 1); - verify(mEvictionCallback, times(0)).onEntryEvicted(mKey1); - } - - @Test - public void evictAll_evictionCallback_called() { - mCache.put(mKey1, 1); - mCache.evictAllCache(); - verify(mEvictionCallback, times(1)).onEntryEvicted(mKey1); - } - - @Test - public void trimAll_evictionCallback_called() { - mCache.put(mKey1, 1); - mCache.put(mKey2, 2); - mCache.trimToSize(-1); - verify(mEvictionCallback, times(1)).onEntryEvicted(mKey1); - verify(mEvictionCallback, times(1)).onEntryEvicted(mKey2); - - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 0a3bc6def160..1d4b4be9e683 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -53,8 +53,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IBatteryStats; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.KeyguardUpdateMonitor.BatteryStatus; import com.android.settingslib.Utils; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index c97813de8c0c..60163f26bb2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -49,6 +49,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -104,7 +106,8 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mock(StatusBarStateControllerImpl.class), mEntryManager, mock(KeyguardBypassController.class), mock(BubbleController.class), - mock(DynamicPrivacyController.class)); + mock(DynamicPrivacyController.class), + mock(ForegroundServiceSectionController.class)); mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 4103edee6255..9d667a9a91c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java @@ -26,8 +26,8 @@ import android.testing.TestableLooper.RunWithLooper; import android.widget.FrameLayout; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 296d0cef715c..5a0a495e1f85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -58,10 +58,13 @@ import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.NotificationVisibility; +import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -79,16 +82,17 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -96,15 +100,18 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Assert; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; @@ -137,11 +144,16 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private RowInflaterTask mAsyncInflationTask; - @Mock private NotifLog mNotifLog; + @Mock private NotificationEntryManagerLogger mLogger; @Mock private FeatureFlags mFeatureFlags; @Mock private LeakDetector mLeakDetector; - @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; - @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder; + @Mock private NotificationMediaManager mNotificationMediaManager; + @Mock private ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; + @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent; + @Mock private FalsingManager mFalsingManager; + @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock private StatusBarStateController mStatusBarStateController; private int mId; private NotificationEntry mEntry; @@ -190,7 +202,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(SmartReplyController.class); - mDependency.injectMockDependency(NotificationMediaManager.class); mCountDownLatch = new CountDownLatch(1); @@ -206,40 +217,35 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mRemoteInputManager); - contentBinder.setInflateSynchronously(true); - - when(mNotificationRowComponentBuilder.activatableNotificationView(any())) - .thenReturn(mNotificationRowComponentBuilder); - when(mNotificationRowComponentBuilder.build()).thenReturn( - () -> mActivatableNotificationViewController); + RowContentBindStage bindStage = mock(RowContentBindStage.class); + when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); NotificationRowBinderImpl notificationRowBinder = new NotificationRowBinderImpl(mContext, + new NotificationMessagingUtil(mContext), mRemoteInputManager, mLockscreenUserManager, - contentBinder, + mock(NotifBindPipeline.class), + bindStage, true, /* allowLongPress */ - mock(KeyguardBypassController.class), - mock(StatusBarStateController.class), + mKeyguardBypassController, + mStatusBarStateController, mGroupManager, mGutsManager, mNotificationInterruptionStateProvider, - () -> new RowInflaterTask(mNotificationRowComponentBuilder), - mock(NotificationLogger.class)); + RowInflaterTask::new, + mExpandableNotificationRowComponentBuilder); when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mEntryManager = new TestableNotificationEntryManager( - mNotifLog, + mLogger, mGroupManager, new NotificationRankingManager( - () -> mock(NotificationMediaManager.class), + () -> mNotificationMediaManager, mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), - mNotifLog, + mLogger, mock(NotificationSectionsFeatureManager.class), mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class)), @@ -247,19 +253,62 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mFeatureFlags, () -> notificationRowBinder, () -> mRemoteInputManager, - mLeakDetector + mLeakDetector, + mock(ForegroundServiceDismissalFeatureController.class) ); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager); mEntryManager.addNotificationEntryListener(mEntryListener); - mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); + mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor); - notificationRowBinder.setUpWithPresenter( - mPresenter, mListContainer, mHeadsUpManager, mBindCallback); + notificationRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback); notificationRowBinder.setInflationCallback(mEntryManager); notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); setUserSentiment( mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL); + + ArgumentCaptor<ExpandableNotificationRow> viewCaptor = + ArgumentCaptor.forClass(ExpandableNotificationRow.class); + when(mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(viewCaptor.capture())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .notificationEntry(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onDismissRunnable(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .inflationCallback(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onExpandClickListener(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + + when(mExpandableNotificationRowComponentBuilder.build()) + .thenReturn(mExpandableNotificationRowComponent); + when(mExpandableNotificationRowComponent.getExpandableNotificationRowController()) + .thenAnswer((Answer<ExpandableNotificationRowController>) invocation -> + new ExpandableNotificationRowController( + viewCaptor.getValue(), + mock(ActivatableNotificationViewController.class), + mNotificationMediaManager, + mock(PluginManager.class), + new FakeSystemClock(), + "FOOBAR", "FOOBAR", + mKeyguardBypassController, + mGroupManager, + bindStage, + mock(NotificationLogger.class), + mHeadsUpManager, + mPresenter, + mStatusBarStateController, + mEntryManager, + mGutsManager, + true, + null, + mFalsingManager + )); } @After @@ -268,7 +317,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.abortTask(); } + // TODO: These tests are closer to functional tests and we should move them to their own file. + // and also strip some of the verifies that make the test too complex @Test + @Ignore public void testAddNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -305,6 +357,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -330,6 +383,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test + @Ignore public void testUpdateNotification_prePostEntryOrder() throws Exception { TestableLooper.get(this).processAllMessages(); @@ -398,7 +452,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); - verify(mRow).setEntry(eq(mEntry)); assertEquals(1, mEntry.getSmartActions().size()); assertEquals("action", mEntry.getSmartActions().get(0).title); verify(mEntryListener).onNotificationRankingUpdated(mRankingMap); @@ -546,7 +599,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN interceptor that intercepts that entry - when(mRemoveInterceptor.onNotificationRemoveRequested(eq(mEntry.getKey()), anyInt())) + when(mRemoveInterceptor.onNotificationRemoveRequested( + eq(mEntry.getKey()), eq(mEntry), anyInt())) .thenReturn(true); // WHEN the notification is removed @@ -564,7 +618,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN interceptor that doesn't intercept - when(mRemoveInterceptor.onNotificationRemoveRequested(eq(mEntry.getKey()), anyInt())) + when(mRemoveInterceptor.onNotificationRemoveRequested( + eq(mEntry.getKey()), eq(mEntry), anyInt())) .thenReturn(false); // WHEN the notification is removed diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index 5aed61b98ad9..1116a333125e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -42,11 +42,11 @@ import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt index 29ce92074027..0e730e5c3ffb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt @@ -22,7 +22,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationRankingManager import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.NotificationGroupManager @@ -34,21 +33,22 @@ import java.util.concurrent.CountDownLatch * Enable some test capabilities for NEM without making everything public on the base class */ class TestableNotificationEntryManager( - log: NotifLog, + logger: NotificationEntryManagerLogger, gm: NotificationGroupManager, rm: NotificationRankingManager, ke: KeyguardEnvironment, ff: FeatureFlags, rb: dagger.Lazy<NotificationRowBinder>, notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>, - leakDetector: LeakDetector -) : NotificationEntryManager(log, gm, rm, ke, ff, rb, - notificationRemoteInputManagerLazy, leakDetector) { + leakDetector: LeakDetector, + fgsFeatureController: ForegroundServiceDismissalFeatureController +) : NotificationEntryManager(logger, gm, rm, ke, ff, rb, + notificationRemoteInputManagerLazy, leakDetector, fgsFeatureController) { public var countDownLatch: CountDownLatch = CountDownLatch(1) - override fun onAsyncInflationFinished(entry: NotificationEntry?, inflatedFlags: Int) { - super.onAsyncInflationFinished(entry, inflatedFlags) + override fun onAsyncInflationFinished(entry: NotificationEntry) { + super.onAsyncInflationFinished(entry) countDownLatch.countDown() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 9a7e97b5d55a..abc0f3ee8a52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -17,9 +17,16 @@ package com.android.systemui.statusbar.notification.collection; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CLICK; +import static android.service.notification.NotificationStats.DISMISSAL_SHADE; +import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; +import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED; +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; +import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -27,6 +34,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -63,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.util.Assert; @@ -77,6 +86,7 @@ import org.mockito.Spy; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -91,11 +101,19 @@ public class NotifCollectionTest extends SysuiTestCase { @Spy private RecordingCollectionListener mCollectionListener; @Mock private CollectionReadyForBuildListener mBuildListener; @Mock private FeatureFlags mFeatureFlags; + @Mock private DismissedByUserStats mDismissedByUserStats; @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1"); @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2"); @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3"); + @Spy private RecordingDismissInterceptor mInterceptor1 = new RecordingDismissInterceptor( + "Interceptor1"); + @Spy private RecordingDismissInterceptor mInterceptor2 = new RecordingDismissInterceptor( + "Interceptor2"); + @Spy private RecordingDismissInterceptor mInterceptor3 = new RecordingDismissInterceptor( + "Interceptor3"); + @Captor private ArgumentCaptor<BatchableNotificationHandler> mListenerCaptor; @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor; @Captor private ArgumentCaptor<Collection<NotificationEntry>> mBuildListCaptor; @@ -139,9 +157,10 @@ public class NotifCollectionTest extends SysuiTestCase { .setRank(4747)); // THEN the listener is notified - verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture()); - + verify(mCollectionListener).onEntryInit(mEntryCaptor.capture()); NotificationEntry entry = mEntryCaptor.getValue(); + + verify(mCollectionListener).onEntryAdded(entry); assertEquals(notif1.key, entry.getKey()); assertEquals(notif1.sbn, entry.getSbn()); assertEquals(notif1.ranking, entry.getRanking()); @@ -235,7 +254,8 @@ public class NotifCollectionTest extends SysuiTestCase { mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL); // THEN the listener is notified - verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false); + verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL); + verify(mCollectionListener).onEntryCleanUp(entry); assertEquals(notif.sbn, entry.getSbn()); assertEquals(notif.ranking, entry.getRanking()); } @@ -320,24 +340,15 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test - public void testDismissNotification() throws RemoteException { - // GIVEN a collection with a couple notifications and a lifetime extender - mCollection.addNotificationLifetimeExtender(mExtender1); - + public void testDismissNotificationSentToSystemServer() throws RemoteException { + // GIVEN a collection with a couple notifications NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); // WHEN a notification is manually dismissed - DismissedByUserStats stats = new DismissedByUserStats( - NotificationStats.DISMISSAL_SHADE, - NotificationStats.DISMISS_SENTIMENT_NEUTRAL, - NotificationVisibility.obtain(entry2.getKey(), 7, 2, true)); - - mCollection.dismissNotification(entry2, REASON_CLICK, stats); - - // THEN we check for lifetime extension - verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK); + DismissedByUserStats stats = defaultStats(entry2); + mCollection.dismissNotification(entry2, defaultStats(entry2)); // THEN we send the dismissal to system server verify(mStatusBarService).onNotificationClear( @@ -349,9 +360,259 @@ public class NotifCollectionTest extends SysuiTestCase { stats.dismissalSurface, stats.dismissalSentiment, stats.notificationVisibility); + } - // THEN we fire a remove event - verify(mCollectionListener).onEntryRemoved(entry2, REASON_CLICK, true); + @Test + public void testDismissedNotificationsAreMarkedAsDismissedLocally() { + // GIVEN a collection with a notification + NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN a notification is manually dismissed + mCollection.dismissNotification(entry1, defaultStats(entry1)); + + // THEN the entry is marked as dismissed locally + assertEquals(DISMISSED, entry1.getDismissState()); + } + + @Test + public void testDismissedNotificationsCannotBeLifetimeExtended() { + // GIVEN a collection with a notification and a lifetime extender + mCollection.addNotificationLifetimeExtender(mExtender1); + NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN a notification is manually dismissed + mCollection.dismissNotification(entry1, defaultStats(entry1)); + + // THEN lifetime extenders are never queried + verify(mExtender1, never()).shouldExtendLifetime(eq(entry1), anyInt()); + } + + @Test + public void testDismissedNotificationsDoNotTriggerRemovalEvents() { + // GIVEN a collection with a notification + NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN a notification is manually dismissed + mCollection.dismissNotification(entry1, defaultStats(entry1)); + + // THEN onEntryRemoved is not called + verify(mCollectionListener, never()).onEntryRemoved(eq(entry1), anyInt()); + } + + @Test + public void testDismissedNotificationsStillAppearInNotificationSet() { + // GIVEN a collection with a notification + NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN a notification is manually dismissed + mCollection.dismissNotification(entry1, defaultStats(entry1)); + + // THEN the dismissed entry still appears in the notification set + assertEquals( + new ArraySet<>(Collections.singletonList(entry1)), + new ArraySet<>(mCollection.getActiveNotifs())); + } + + @Test + public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() { + // GIVEN A notif group with one summary and two children + mCollection.addNotificationLifetimeExtender(mExtender1); + NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag") + .setGroup(mContext, GROUP_1) + .setGroupSummary(mContext, true)); + NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag") + .setGroup(mContext, GROUP_1)); + NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3, "myTag") + .setGroup(mContext, GROUP_1)); + + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key); + + // GIVEN that the summary and one child are retracted, but both are lifetime-extended + mExtender1.shouldExtendLifetime = true; + mNoMan.retractNotif(notif1.sbn, REASON_CANCEL); + mNoMan.retractNotif(notif2.sbn, REASON_CANCEL); + assertEquals( + new ArraySet<>(List.of(entry1, entry2, entry3)), + new ArraySet<>(mCollection.getActiveNotifs())); + + // WHEN the summary is dismissed by the user + mCollection.dismissNotification(entry1, defaultStats(entry1)); + + // THEN the summary is removed, but both children stick around + assertEquals( + new ArraySet<>(List.of(entry2, entry3)), + new ArraySet<>(mCollection.getActiveNotifs())); + assertEquals(NOT_DISMISSED, entry2.getDismissState()); + assertEquals(NOT_DISMISSED, entry3.getDismissState()); + } + + @Test + public void testDismissInterceptorsAreCalled() throws RemoteException { + // GIVEN a collection with notifications with multiple dismiss interceptors + mInterceptor1.shouldInterceptDismissal = true; + mInterceptor2.shouldInterceptDismissal = true; + mInterceptor3.shouldInterceptDismissal = false; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + mCollection.addNotificationDismissInterceptor(mInterceptor2); + mCollection.addNotificationDismissInterceptor(mInterceptor3); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // WHEN a notification is manually dismissed + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + + // THEN all interceptors get checked + verify(mInterceptor1).shouldInterceptDismissal(entry); + verify(mInterceptor2).shouldInterceptDismissal(entry); + verify(mInterceptor3).shouldInterceptDismissal(entry); + assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors); + + // THEN we never send the dismissal to system server + verify(mStatusBarService, never()).onNotificationClear( + notif.sbn.getPackageName(), + notif.sbn.getTag(), + 47, + notif.sbn.getUser().getIdentifier(), + notif.sbn.getKey(), + stats.dismissalSurface, + stats.dismissalSentiment, + stats.notificationVisibility); + } + + @Test + public void testDismissInterceptorsCanceledWhenNotifIsUpdated() throws RemoteException { + // GIVEN a few lifetime extenders and a couple notifications + mCollection.addNotificationDismissInterceptor(mInterceptor1); + mCollection.addNotificationDismissInterceptor(mInterceptor2); + + mInterceptor1.shouldInterceptDismissal = true; + mInterceptor2.shouldInterceptDismissal = true; + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // WHEN a notification is manually dismissed and intercepted + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors); + clearInvocations(mInterceptor1, mInterceptor2); + + // WHEN the notification is reposted + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + + // THEN all of the active dismissal interceptors are canceled + verify(mInterceptor1).cancelDismissInterception(entry); + verify(mInterceptor2).cancelDismissInterception(entry); + assertEquals(List.of(), entry.mDismissInterceptors); + + // THEN the notification is never sent to system server to dismiss + verify(mStatusBarService, never()).onNotificationClear( + eq(notif.sbn.getPackageName()), + eq(notif.sbn.getTag()), + eq(47), + eq(notif.sbn.getUser().getIdentifier()), + eq(notif.sbn.getKey()), + anyInt(), + anyInt(), + anyObject()); + } + + @Test + public void testEndingAllDismissInterceptorsSendsDismiss() throws RemoteException { + // GIVEN a collection with notifications a dismiss interceptor + mInterceptor1.shouldInterceptDismissal = true; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // GIVEN a notification is manually dismissed + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + + // WHEN all interceptors end their interception dismissal + mInterceptor1.shouldInterceptDismissal = false; + mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, + mDismissedByUserStats); + + // THEN we send the dismissal to system server + verify(mStatusBarService, times(1)).onNotificationClear( + eq(notif.sbn.getPackageName()), + eq(notif.sbn.getTag()), + eq(47), + eq(notif.sbn.getUser().getIdentifier()), + eq(notif.sbn.getKey()), + anyInt(), + anyInt(), + anyObject()); + } + + @Test + public void testEndDismissInterceptionUpdatesDismissInterceptors() throws RemoteException { + // GIVEN a collection with notifications with multiple dismiss interceptors + mInterceptor1.shouldInterceptDismissal = true; + mInterceptor2.shouldInterceptDismissal = true; + mInterceptor3.shouldInterceptDismissal = false; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + mCollection.addNotificationDismissInterceptor(mInterceptor2); + mCollection.addNotificationDismissInterceptor(mInterceptor3); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // GIVEN a notification is manually dismissed + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + mCollection.dismissNotification(entry, stats); + + // WHEN an interceptor ends its interception + mInterceptor1.shouldInterceptDismissal = false; + mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, + mDismissedByUserStats); + + // THEN all interceptors get checked + verify(mInterceptor1).shouldInterceptDismissal(entry); + verify(mInterceptor2).shouldInterceptDismissal(entry); + verify(mInterceptor3).shouldInterceptDismissal(entry); + + // THEN mInterceptor2 is the only dismiss interceptor + assertEquals(List.of(mInterceptor2), entry.mDismissInterceptors); + } + + + @Test(expected = IllegalStateException.class) + public void testEndingDismissalOfNonInterceptedThrows() throws RemoteException { + // GIVEN a collection with notifications with a dismiss interceptor that hasn't been called + mInterceptor1.shouldInterceptDismissal = false; + mCollection.addNotificationDismissInterceptor(mInterceptor1); + + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + // WHEN we try to end the dismissal of an interceptor that didn't intercept the notif + mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, + mDismissedByUserStats); + + // THEN an exception is thrown } @Test(expected = IllegalStateException.class) @@ -364,15 +625,115 @@ public class NotifCollectionTest extends SysuiTestCase { mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); // WHEN we try to dismiss a notification that isn't present - mCollection.dismissNotification( - entry2, - REASON_CLICK, - new DismissedByUserStats(0, 0, NotificationVisibility.obtain("foo", 47, 3, true))); + mCollection.dismissNotification(entry2, defaultStats(entry2)); // THEN an exception is thrown } @Test + public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() { + // GIVEN a collection with two grouped notifs in it + NotifEvent notif0 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0) + .setGroup(mContext, GROUP_1) + .setGroupSummary(mContext, true)); + NotifEvent notif1 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, GROUP_1)); + NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN the summary is dismissed + mCollection.dismissNotification(entry0, defaultStats(entry0)); + + // THEN all members of the group are marked as dismissed locally + assertEquals(DISMISSED, entry0.getDismissState()); + assertEquals(PARENT_DISMISSED, entry1.getDismissState()); + } + + @Test + public void testUpdatingDismissedSummaryBringsChildrenBack() { + // GIVEN a collection with two grouped notifs in it + NotifEvent notif0 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0) + .setGroup(mContext, GROUP_1) + .setGroupSummary(mContext, true)); + NotifEvent notif1 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, GROUP_1)); + NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN the summary is dismissed but then reposted without a group + mCollection.dismissNotification(entry0, defaultStats(entry0)); + NotifEvent notif0a = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0)); + + // THEN it and all of its previous children are no longer dismissed locally + assertEquals(NOT_DISMISSED, entry0.getDismissState()); + assertEquals(NOT_DISMISSED, entry1.getDismissState()); + } + + @Test + public void testDismissedChildrenAreNotResetByParentUpdate() { + // GIVEN a collection with three grouped notifs in it + NotifEvent notif0 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0) + .setGroup(mContext, GROUP_1) + .setGroupSummary(mContext, true)); + NotifEvent notif1 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, GROUP_1)); + NotifEvent notif2 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 2) + .setGroup(mContext, GROUP_1)); + NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // WHEN a child is dismissed, then the parent is dismissed, then the parent is updated + mCollection.dismissNotification(entry1, defaultStats(entry1)); + mCollection.dismissNotification(entry0, defaultStats(entry0)); + NotifEvent notif0a = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0)); + + // THEN the manually-dismissed child is still marked as dismissed + assertEquals(NOT_DISMISSED, entry0.getDismissState()); + assertEquals(DISMISSED, entry1.getDismissState()); + assertEquals(NOT_DISMISSED, entry2.getDismissState()); + } + + @Test + public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() { + // GIVEN a collection with two grouped notifs in it + NotifEvent notif0 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0) + .setOverrideGroupKey(GROUP_1) + .setGroupSummary(mContext, true)); + NotifEvent notif1 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 1) + .setOverrideGroupKey(GROUP_1)); + NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN the summary is dismissed but then reposted AND in the same update one of the + // children's ranking loses its override group + mCollection.dismissNotification(entry0, defaultStats(entry0)); + mNoMan.setRanking(entry1.getKey(), new RankingBuilder() + .setKey(entry1.getKey()) + .build()); + mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0) + .setOverrideGroupKey(GROUP_1) + .setGroupSummary(mContext, true)); + + // THEN it and all of its previous children are no longer dismissed locally, including the + // child that is no longer part of the group + assertEquals(NOT_DISMISSED, entry0.getDismissState()); + assertEquals(NOT_DISMISSED, entry1.getDismissState()); + } + + @Test public void testLifetimeExtendersAreQueriedWhenNotifRemoved() { // GIVEN a couple notifications and a few lifetime extenders mExtender1.shouldExtendLifetime = true; @@ -387,12 +748,12 @@ public class NotifCollectionTest extends SysuiTestCase { NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); // WHEN a notification is removed - mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); + mNoMan.retractNotif(notif2.sbn, REASON_CLICK); // THEN each extender is asked whether to extend, even if earlier ones return true - verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN); - verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN); - verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); + verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK); + verify(mExtender2).shouldExtendLifetime(entry2, REASON_CLICK); + verify(mExtender3).shouldExtendLifetime(entry2, REASON_CLICK); // THEN the entry is not removed assertTrue(mCollection.getActiveNotifs().contains(entry2)); @@ -426,9 +787,9 @@ public class NotifCollectionTest extends SysuiTestCase { mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); // THEN each extender is re-queried - verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN); - verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN); - verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); + verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry is not removed assertTrue(mCollection.getActiveNotifs().contains(entry2)); @@ -464,9 +825,9 @@ public class NotifCollectionTest extends SysuiTestCase { assertTrue(mCollection.getActiveNotifs().contains(entry2)); // THEN we don't re-query the extenders - verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt()); - verify(mExtender2, never()).shouldExtendLifetime(eq(entry2), anyInt()); - verify(mExtender3, never()).shouldExtendLifetime(eq(entry2), anyInt()); + verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry properly records all extenders that returned true assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders); @@ -499,7 +860,7 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN the entry removed assertFalse(mCollection.getActiveNotifs().contains(entry2)); - verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false); + verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN); } @Test @@ -589,6 +950,36 @@ public class NotifCollectionTest extends SysuiTestCase { assertEquals(notif2a.ranking, entry2.getRanking()); } + @Test + public void testCancellationReasonIsSetWhenNotifIsCancelled() { + // GIVEN a notification + NotifEvent notif0 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); + NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); + + // WHEN the notification is retracted + mNoMan.retractNotif(notif0.sbn, REASON_APP_CANCEL); + + // THEN the retraction reason is stored on the notif + assertEquals(REASON_APP_CANCEL, entry0.mCancellationReason); + } + + @Test + public void testCancellationReasonIsClearedWhenNotifIsUpdated() { + // GIVEN a notification and a lifetime extender that will preserve it + NotifEvent notif0 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); + NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); + mCollection.addNotificationLifetimeExtender(mExtender1); + mExtender1.shouldExtendLifetime = true; + + // WHEN the notification is retracted and subsequently reposted + mNoMan.retractNotif(notif0.sbn, REASON_APP_CANCEL); + assertEquals(REASON_APP_CANCEL, entry0.mCancellationReason); + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); + + // THEN the notification has its cancellation reason cleared + assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason); + } + private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) { return new NotificationEntryBuilder() .setPkg(pkg) @@ -602,10 +993,21 @@ public class NotifCollectionTest extends SysuiTestCase { .setId(id); } + private static DismissedByUserStats defaultStats(NotificationEntry entry) { + return new DismissedByUserStats( + DISMISSAL_SHADE, + DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); + } + private static class RecordingCollectionListener implements NotifCollectionListener { private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>(); @Override + public void onEntryInit(NotificationEntry entry) { + } + + @Override public void onEntryAdded(NotificationEntry entry) { mLastSeenEntries.put(entry.getKey(), entry); } @@ -615,7 +1017,11 @@ public class NotifCollectionTest extends SysuiTestCase { } @Override - public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) { + public void onEntryRemoved(NotificationEntry entry, int reason) { + } + + @Override + public void onEntryCleanUp(NotificationEntry entry) { } public NotificationEntry getEntry(String key) { @@ -662,6 +1068,38 @@ public class NotifCollectionTest extends SysuiTestCase { } } + private static class RecordingDismissInterceptor implements NotifDismissInterceptor { + private final String mName; + + public @Nullable OnEndDismissInterception onEndInterceptionCallback; + public boolean shouldInterceptDismissal = false; + + private RecordingDismissInterceptor(String name) { + mName = name; + } + + @Override + public String getName() { + return mName; + } + + @Override + public void setCallback(OnEndDismissInterception callback) { + this.onEndInterceptionCallback = callback; + } + + @Override + public boolean shouldInterceptDismissal(NotificationEntry entry) { + return shouldInterceptDismissal; + } + + @Override + public void cancelDismissInterception(NotificationEntry entry) { + } + } + private static final String TEST_PACKAGE = "com.android.test.collection"; private static final String TEST_PACKAGE2 = "com.android.test.collection2"; + + private static final String GROUP_1 = "group_1"; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index 7ab4846ea066..c6b496dd8215 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -27,10 +27,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider -import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT @@ -62,7 +62,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { mock(NotificationGroupManager::class.java), mock(HeadsUpManager::class.java), mock(NotificationFilter::class.java), - mock(NotifLog::class.java), + mock(NotificationEntryManagerLogger::class.java), mock(NotificationSectionsFeatureManager::class.java), personNotificationIdentifier, HighPriorityProvider(personNotificationIdentifier) @@ -189,7 +189,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { groupManager: NotificationGroupManager, headsUpManager: HeadsUpManager, filter: NotificationFilter, - notifLog: NotifLog, + logger: NotificationEntryManagerLogger, sectionsFeatureManager: NotificationSectionsFeatureManager, peopleNotificationIdentifier: PeopleNotificationIdentifier, highPriorityProvider: HighPriorityProvider @@ -198,7 +198,7 @@ class NotificationRankingManagerTest : SysuiTestCase() { groupManager, headsUpManager, filter, - notifLog, + logger, sectionsFeatureManager, peopleNotificationIdentifier, highPriorityProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 3d79ce15bfb6..a8918103c4a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -21,7 +21,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,7 +50,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; @@ -147,15 +145,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void setNeedsRedactionSetsInflationFlag() throws Exception { - ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - - row.setNeedsRedaction(true); - - assertTrue(row.isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC)); - } - - @Test public void setNeedsRedactionFreesViewWhenFalse() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); row.setNeedsRedaction(true); @@ -205,9 +194,8 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testClickSound() throws Exception { assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled()); - StatusBarStateController mock = mock(StatusBarStateController.class); + StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController(); when(mock.isDozing()).thenReturn(true); - mGroupRow.setStatusBarStateController(mock); mGroupRow.setSecureStateProvider(()-> false); assertFalse("Shouldn't play sounds when dark and trusted.", mGroupRow.isSoundEffectsEnabled()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java new file mode 100644 index 000000000000..8f9f65d12762 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -0,0 +1,158 @@ +/* + * 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.row; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.annotation.NonNull; +import androidx.core.os.CancellationSignal; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotifBindPipelineTest extends SysuiTestCase { + + private NotifBindPipeline mBindPipeline; + private TestBindStage mStage = new TestBindStage(); + + @Mock private NotificationEntry mEntry; + @Mock private ExpandableNotificationRow mRow; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + + mBindPipeline = new NotifBindPipeline(entryManager); + mBindPipeline.setStage(mStage); + + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + NotificationEntryListener entryListener = entryListenerCaptor.getValue(); + + entryListener.onPendingEntryAdded(mEntry); + } + + @Test + public void testCallbackCalled() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // WHEN content is invalidated + BindCallback callback = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback); + + // WHEN stage finishes its work + mStage.doWorkSynchronously(); + + // THEN the callback is called when bind finishes + verify(callback).onBindFinished(mEntry); + } + + @Test + public void testCallbackCancelled() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // GIVEN an in-progress pipeline run + BindCallback callback = mock(BindCallback.class); + CancellationSignal signal = mStage.requestRebind(mEntry, callback); + + // WHEN the callback is cancelled. + signal.cancel(); + + // WHEN the stage finishes all its work + mStage.doWorkSynchronously(); + + // THEN the callback is not called when bind finishes + verify(callback, never()).onBindFinished(mEntry); + } + + @Test + public void testMultipleCallbacks() { + // GIVEN a bound row + mBindPipeline.manageRow(mEntry, mRow); + + // WHEN the pipeline is invalidated. + BindCallback callback = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback); + + // WHEN the pipeline is invalidated again before the work completes. + BindCallback callback2 = mock(BindCallback.class); + mStage.requestRebind(mEntry, callback2); + + // WHEN the stage finishes all work. + mStage.doWorkSynchronously(); + + // THEN both callbacks are called when the bind finishes + verify(callback).onBindFinished(mEntry); + verify(callback2).onBindFinished(mEntry); + } + + /** + * Bind stage for testing where asynchronous work can be synchronously controlled. + */ + private static class TestBindStage extends BindStage { + private List<Runnable> mExecutionRequests = new ArrayList<>(); + + @Override + protected void executeStage(@NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row, @NonNull StageCallback callback) { + mExecutionRequests.add(() -> callback.onStageFinished(entry)); + } + + @Override + protected void abortStage(@NonNull NotificationEntry entry, + @NonNull ExpandableNotificationRow row) { + + } + + @Override + protected Object newStageParams() { + return null; + } + + public void doWorkSynchronously() { + for (Runnable work: mExecutionRequests) { + work.run(); + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java index d7214f3b9228..20cc01accbc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java @@ -32,10 +32,10 @@ import android.widget.RemoteViews; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import org.junit.Before; import org.junit.Test; @@ -50,7 +50,7 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { private NotifRemoteViewCacheImpl mNotifRemoteViewCache; private NotificationEntry mEntry; - private NotificationEntryListener mEntryListener; + private NotifCollectionListener mEntryListener; @Mock private RemoteViews mRemoteViews; @Before @@ -58,19 +58,17 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mEntry = new NotificationEntryBuilder().build(); - NotificationEntryManager entryManager = mock(NotificationEntryManager.class); - mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager); - ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = - ArgumentCaptor.forClass(NotificationEntryListener.class); - verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + CommonNotifCollection collection = mock(CommonNotifCollection.class); + mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(collection); + ArgumentCaptor<NotifCollectionListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotifCollectionListener.class); + verify(collection).addCollectionListener(entryListenerCaptor.capture()); mEntryListener = entryListenerCaptor.getValue(); + mEntryListener.onEntryInit(mEntry); } @Test public void testPutCachedView() { - // GIVEN an initialized cache for an entry. - mEntryListener.onPendingEntryAdded(mEntry); - // WHEN a notification's cached remote views is put in. mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); @@ -85,7 +83,6 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { @Test public void testRemoveCachedView() { // GIVEN a cache with a cached view. - mEntryListener.onPendingEntryAdded(mEntry); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); // WHEN we remove the cached view. @@ -98,7 +95,6 @@ public class NotifRemoteViewCacheImplTest extends SysuiTestCase { @Test public void testClearCache() { // GIVEN a non-empty cache. - mEntryListener.onPendingEntryAdded(mEntry); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java index 444a6e5b4b13..1dfe7bc33373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java @@ -46,7 +46,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.util.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index cb9da6a40cb9..8a42e5fb4ea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -49,9 +49,7 @@ import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; @@ -200,8 +198,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { countDownLatch.countDown(); } }, mRow.getPrivateLayout(), null, null, new HashMap<>(), @@ -219,34 +216,6 @@ public class NotificationContentInflaterTest extends SysuiTestCase { assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } - /* Cancelling requires us to be on the UI thread otherwise we might have a race */ - @Test - public void testSupersedesExistingTask() { - mNotificationInflater.bindContent( - mRow.getEntry(), - mRow, - FLAG_CONTENT_VIEW_ALL, - new BindParams(), - false /* forceInflate */, - null /* callback */); - - // Trigger inflation of contracted only. - mNotificationInflater.bindContent( - mRow.getEntry(), - mRow, - FLAG_CONTENT_VIEW_CONTRACTED, - new BindParams(), - false /* forceInflate */, - null /* callback */); - - InflationTask runningTask = mRow.getEntry().getRunningTask(); - NotificationContentInflater.AsyncInflationTask asyncInflationTask = - (NotificationContentInflater.AsyncInflationTask) runningTask; - assertEquals("Successive inflations don't inherit the previous flags!", - FLAG_CONTENT_VIEW_ALL, asyncInflationTask.getReInflateFlags()); - runningTask.abort(); - } - @Test public void doesntReapplyDisallowedRemoteView() throws Exception { mBuilder.setStyle(new Notification.MediaStyle()); @@ -349,8 +318,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { + public void onAsyncInflationFinished(NotificationEntry entry) { if (expectingException) { exceptionHolder.setException(new RuntimeException( "Inflation finished even though there should be an error")); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 20a089f97f51..e8de10f7392b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.phone.ShadeController; import org.junit.Before; import org.junit.Rule; @@ -131,6 +132,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { private ShortcutManager mShortcutManager; @Mock private NotificationGuts mNotificationGuts; + @Mock + private ShadeController mShadeController; @Before public void setUp() throws Exception { @@ -139,12 +142,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mDependency.injectTestDependency(BubbleController.class, mBubbleController); + mDependency.injectTestDependency(ShadeController.class, mShadeController); // Inflate the layout final LayoutInflater layoutInflater = LayoutInflater.from(mContext); mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate( R.layout.notification_conversation_info, null); - mNotificationInfo.mShowHomeScreen = true; mNotificationInfo.setGutsParent(mNotificationGuts); doAnswer((Answer<Object>) invocation -> { mNotificationInfo.handleCloseControls(true, false); @@ -173,7 +176,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { when(mShortcutInfo.getShortLabel()).thenReturn("Convo name"); List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); - mImage = mContext.getDrawable(R.drawable.ic_star); + mImage = mContext.getDrawable(R.drawable.ic_remove); when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo), anyInt())).thenReturn(mImage); @@ -333,8 +336,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(GONE, dividerView.getVisibility()); } @Test @@ -364,8 +365,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(VISIBLE, dividerView.getVisibility()); } @Test @@ -502,6 +501,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); + verify(mShadeController).animateCollapsePanels(); } @Test @@ -644,9 +644,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_favorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -655,13 +655,13 @@ public class NotificationConversationInfoTest extends SysuiTestCase { ArgumentCaptor.forClass(NotificationChannel.class); verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( anyString(), anyInt(), captor.capture()); - assertTrue(captor.getValue().canBypassDnd()); + assertTrue(captor.getValue().isImportantConversation()); } @Test public void testFavorite_unfavorite() throws Exception { - mNotificationChannel.setBypassDnd(true); - mConversationChannel.setBypassDnd(true); + mNotificationChannel.setImportantConversation(true); + mConversationChannel.setImportantConversation(true); mNotificationInfo.bindNotification( mShortcutManager, @@ -677,9 +677,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_favorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -688,35 +688,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { ArgumentCaptor.forClass(NotificationChannel.class); verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( anyString(), anyInt(), captor.capture()); - assertFalse(captor.getValue().canBypassDnd()); - } - - @Test - public void testDemote() throws Exception { - mNotificationInfo.bindNotification( - mShortcutManager, - mLauncherApps, - mMockPackageManager, - mMockINotificationManager, - mVisualStabilityManager, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - true); - - - ImageButton demote = mNotificationInfo.findViewById(R.id.demote); - demote.performClick(); - mTestableLooper.processAllMessages(); - - ArgumentCaptor<NotificationChannel> captor = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), anyInt(), captor.capture()); - assertTrue(captor.getValue().isDemoted()); + assertFalse(captor.getValue().isImportantConversation()); } @Test @@ -738,9 +710,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_mute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_unmute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); @@ -774,9 +746,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_unmute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_mute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 4e27770982e5..bbb6723135a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -66,7 +66,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 457bbe23334b..35b55087873b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar; +package com.android.systemui.statusbar.notification.row; import static android.app.Notification.FLAG_BUBBLE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanki import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.annotation.Nullable; import android.app.ActivityManager; @@ -40,17 +41,21 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.notification.NotificationEntryListener; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -58,6 +63,8 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.tests.R; +import org.mockito.ArgumentCaptor; + import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -82,6 +89,10 @@ public class NotificationTestHelper { private final NotificationGroupManager mGroupManager; private ExpandableNotificationRow mRow; private HeadsUpManagerPhone mHeadsUpManager; + private final NotifBindPipeline mBindPipeline; + private final NotificationEntryListener mBindPipelineEntryListener; + private final RowContentBindStage mBindStage; + private StatusBarStateController mStatusBarStateController; public NotificationTestHelper(Context context, TestableDependency dependency) { mContext = context; @@ -89,12 +100,29 @@ public class NotificationTestHelper { dependency.injectMockDependency(BubbleController.class); dependency.injectMockDependency(NotificationShadeWindowController.class); dependency.injectMockDependency(SmartReplyController.class); - StatusBarStateController stateController = mock(StatusBarStateController.class); - mGroupManager = new NotificationGroupManager(stateController); - mHeadsUpManager = new HeadsUpManagerPhone(mContext, stateController, + mStatusBarStateController = mock(StatusBarStateController.class); + mGroupManager = new NotificationGroupManager(mStatusBarStateController); + mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController, mock(KeyguardBypassController.class)); mHeadsUpManager.setUp(null, mGroupManager, null, null); mGroupManager.setHeadsUpManager(mHeadsUpManager); + + + NotificationContentInflater contentBinder = new NotificationContentInflater( + mock(NotifRemoteViewCache.class), + mock(NotificationRemoteInputManager.class)); + contentBinder.setInflateSynchronously(true); + mBindStage = new RowContentBindStage(contentBinder, mock(IStatusBarService.class)); + + NotificationEntryManager entryManager = mock(NotificationEntryManager.class); + + mBindPipeline = new NotifBindPipeline(entryManager); + mBindPipeline.setStage(mBindStage); + + ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = + ArgumentCaptor.forClass(NotificationEntryListener.class); + verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); + mBindPipelineEntryListener = entryListenerCaptor.getValue(); } /** @@ -173,9 +201,17 @@ public class NotificationTestHelper { /** * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. */ + public ExpandableNotificationRow createBubbleInGroup() + throws Exception { + return createBubble(makeBubbleMetadata(null), PKG, true); + } + + /** + * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. + */ public ExpandableNotificationRow createBubble() throws Exception { - return createBubble(makeBubbleMetadata(null), PKG); + return createBubble(makeBubbleMetadata(null), PKG, false); } /** @@ -185,7 +221,7 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent) throws Exception { - return createBubble(makeBubbleMetadata(deleteIntent), PKG); + return createBubble(makeBubbleMetadata(deleteIntent), PKG, false); } /** @@ -195,8 +231,14 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg) throws Exception { + return createBubble(bubbleMetadata, pkg, false); + } + + private ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg, + boolean inGroup) + throws Exception { Notification n = createNotification(false /* isGroupSummary */, - null /* groupKey */, bubbleMetadata); + inGroup ? GROUP_KEY : null /* groupKey */, bubbleMetadata); n.flags |= FLAG_BUBBLE; ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); @@ -281,6 +323,10 @@ public class NotificationTestHelper { return notificationBuilder.build(); } + public StatusBarStateController getStatusBarStateController() { + return mStatusBarStateController; + } + private ExpandableNotificationRow generateRow( Notification notification, String pkg, @@ -331,10 +377,8 @@ public class NotificationTestHelper { entry.createIcons(mContext, entry.getSbn()); row.setEntry(entry); - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mock(NotificationRemoteInputManager.class)); - contentBinder.setInflateSynchronously(true); + mBindPipelineEntryListener.onPendingEntryAdded(entry); + mBindPipeline.manageRow(entry, row); row.initialize( APP_NAME, @@ -343,12 +387,15 @@ public class NotificationTestHelper { mock(KeyguardBypassController.class), mGroupManager, mHeadsUpManager, - contentBinder, - mock(OnExpandClickListener.class)); + mBindStage, + mock(OnExpandClickListener.class), + mock(NotificationMediaManager.class), + mock(ExpandableNotificationRow.OnAppOpsClickListener.class), + mock(FalsingManager.class), + mStatusBarStateController); row.setAboveShelfChangedListener(aboveShelf -> { }); - - row.setInflationFlags(extraInflationFlags); - inflateAndWait(row); + mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); + inflateAndWait(entry, mBindStage); // This would be done as part of onAsyncInflationFinished, but we skip large amounts of // the callback chain, so we need to make up for not adding it to the group manager @@ -357,24 +404,10 @@ public class NotificationTestHelper { return row; } - private static void inflateAndWait(ExpandableNotificationRow row) throws Exception { + private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage) + throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); - NotificationContentInflater.InflationCallback callback = - new NotificationContentInflater.InflationCallback() { - @Override - public void handleInflationException(NotificationEntry entry, - Exception e) { - countDownLatch.countDown(); - } - - @Override - public void onAsyncInflationFinished(NotificationEntry entry, - int inflatedFlags) { - countDownLatch.countDown(); - } - }; - row.setInflationCallback(callback); - row.inflateViews(); + stage.requestRebind(entry, en -> countDownLatch.countDown()); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java new file mode 100644 index 000000000000..775f722b13f9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; + +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 RowContentBindStageTest extends SysuiTestCase { + + private RowContentBindStage mRowContentBindStage; + + @Mock private NotificationRowContentBinder mBinder; + @Mock private NotificationEntry mEntry; + @Mock private ExpandableNotificationRow mRow; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mRowContentBindStage = new RowContentBindStage(mBinder, + mock(IStatusBarService.class)); + mRowContentBindStage.createStageParams(mEntry); + } + + @Test + public void testRequireContentViews() { + // WHEN inflation flags are set and pipeline is invalidated. + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(flags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder binds inflation flags. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(flags), + any(), + anyBoolean(), + any()); + } + + @Test + public void testFreeContentViews() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + + // WHEN inflation flags are cleared and stage executed. + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.freeContentViews(flags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder unbinds flags. + verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags)); + } + + @Test + public void testRebindAllContentViews() { + // GIVEN a view with content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.requireContentViews(flags); + params.clearDirtyContentViews(); + + // WHEN we request rebind and stage executed. + params.rebindAllContentViews(); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder binds inflation flags. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(flags), + any(), + anyBoolean(), + any()); + } + + @Test + public void testSetUseLowPriority() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN low priority is set and stage executed. + params.setUseLowPriority(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use low priority and contracted/expanded are called to bind. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.isLowPriority); + } + + @Test + public void testSetUseGroupInChild() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use group is set and stage executed. + params.setUseChildInGroup(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use group view and contracted/expanded are called to bind. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.isChildInGroup); + } + + @Test + public void testSetUseIncreasedHeight() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use increased height is set and stage executed. + params.setUseIncreasedCollapsedHeight(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with group view and contracted is bound. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_CONTRACTED), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.usesIncreasedHeight); + } + + @Test + public void testSetUseIncreasedHeadsUpHeight() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN use increased heads up height is set and stage executed. + params.setUseIncreasedHeadsUpHeight(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with use group view and heads up is bound. + ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_HEADS_UP), + bindParamsCaptor.capture(), + anyBoolean(), + any()); + BindParams usedParams = bindParamsCaptor.getValue(); + assertTrue(usedParams.usesIncreasedHeadsUpHeight); + } + + @Test + public void testSetNeedsReinflation() { + // GIVEN a view with all content bound. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.requireContentViews(FLAG_CONTENT_VIEW_ALL); + params.clearDirtyContentViews(); + + // WHEN needs reinflation is set. + params.setNeedsReinflation(true); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with forceInflate and all views are requested to bind. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(FLAG_CONTENT_VIEW_ALL), + any(), + eq(true), + any()); + } + + @Test + public void testSupersedesPreviousContentViews() { + // GIVEN a view with content view bind already in progress. + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + int defaultFlags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; + params.requireContentViews(defaultFlags); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // WHEN we bind with another content view before the first finishes. + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); + + // THEN binder is called with BOTH content views. + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(defaultFlags), + any(), + anyBoolean(), + any()); + verify(mBinder).bindContent( + eq(mEntry), + any(), + eq(defaultFlags | FLAG_CONTENT_VIEW_HEADS_UP), + any(), + anyBoolean(), + any()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index d280f185edd3..0790cb7ca6c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -25,8 +25,8 @@ import android.widget.RemoteViews; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.tests.R; import org.junit.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java index 4f45f680f475..038eff7fa5dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java @@ -38,8 +38,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index 14e2fded6cdc..9567f3386dda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -29,8 +29,8 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.util.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index ddd2884ec311..1773175450ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -25,8 +25,8 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import org.junit.Assert; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index 34a309f1d80c..2d1bc7890aed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -30,12 +30,11 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.util.DeviceConfigProxy; @@ -59,8 +58,6 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private ExpandableNotificationRow mFirst; private ExpandableNotificationRow mSecond; @Mock - private StatusBarStateController mStatusBarStateController; - @Mock private KeyguardBypassController mBypassController; @Before @@ -150,13 +147,12 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mSecond), createSection(null, null) }); - ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency) - .createRow(); + NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + ExpandableNotificationRow row = testHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.getRow()).thenReturn(row); - when(mStatusBarStateController.isDozing()).thenReturn(true); - row.setStatusBarStateController(mStatusBarStateController); + when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true); row.setHeadsUp(true); mRoundnessManager.onHeadsUpStateChanged(entry, true); Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 7602e45c1672..b16e52ce7bd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -54,6 +54,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -62,7 +63,9 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.TestableNotificationEntryManager; @@ -72,7 +75,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.FooterView; @@ -130,6 +132,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Mock private ZenModeController mZenModeController; @Mock private NotificationSectionsManager mNotificationSectionsManager; @Mock private NotificationSection mNotificationSection; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + private UserChangedListener mUserChangedListener; private TestableNotificationEntryManager mEntryManager; private int mOriginalInterruptionModelSetting; @@ -156,15 +160,17 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mDependency.injectMockDependency(ShadeController.class); when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); + ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor + .forClass(UserChangedListener.class); mEntryManager = new TestableNotificationEntryManager( - mock(NotifLog.class), + mock(NotificationEntryManagerLogger.class), mock(NotificationGroupManager.class), new NotificationRankingManager( () -> mock(NotificationMediaManager.class), mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), - mock(NotifLog.class), + mock(NotificationEntryManagerLogger.class), mock(NotificationSectionsFeatureManager.class), mock(PeopleNotificationIdentifier.class), mock(HighPriorityProvider.class) @@ -173,7 +179,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mock(FeatureFlags.class), () -> mock(NotificationRowBinder.class), () -> mRemoteInputManager, - mock(LeakDetector.class)); + mock(LeakDetector.class), + mock(ForegroundServiceDismissalFeatureController.class) + ); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager); @@ -195,10 +203,15 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mHeadsUpManager, mKeyguardBypassController, new FalsingManagerFake(), - mock(NotificationLockscreenUserManager.class), + mLockscreenUserManager, mock(NotificationGutsManager.class), mZenModeController, - mNotificationSectionsManager); + mNotificationSectionsManager, + mock(ForegroundServiceSectionController.class), + mock(ForegroundServiceDismissalFeatureController.class) + ); + verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture()); + mUserChangedListener = userChangedCaptor.getValue(); mStackScroller = spy(mStackScrollerInternal); mStackScroller.setShelf(notificationShelf); mStackScroller.setStatusBar(mBar); @@ -279,6 +292,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + public void testOnStatePostChange_verifyIfProfileIsPublic() { + mUserChangedListener.onUserChanged(0); + verify(mLockscreenUserManager).isAnyProfilePublicMode(); + } + + @Test public void manageNotifications_visible() { FooterView view = mock(FooterView.class); mStackScroller.setFooterView(view); @@ -381,8 +400,11 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScroller.onUpdateRowStates(); + // Expecting the footer to be the last child + int expected = mStackScroller.getChildCount() - 1; + // move footer to end - verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(-1 /* end */)); + verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(expected)); } @Test 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 7448dbd0d116..f71d0fc4b43e 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 @@ -35,9 +35,9 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.HeadsUpStatusBarView; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index 5b54fba5b3b5..e171a2894d2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -16,8 +16,12 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -38,6 +42,9 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; @@ -47,6 +54,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -62,8 +70,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { private NotificationGroupManager mGroupManager; private HeadsUpManager mHeadsUpManager; @Mock private NotificationEntryManager mNotificationEntryManager; - @Captor - private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; + @Mock private RowContentBindStage mBindStage; + @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor; private NotificationEntryListener mNotificationEntryListener; private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>(); private final NotificationGroupTestHelper mGroupTestHelper = @@ -72,6 +80,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { @Before public void setup() { + MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(BubbleController.class); mHeadsUpManager = new HeadsUpManager(mContext) {}; @@ -82,7 +91,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(); + when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); + + mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage); mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager); @@ -97,6 +108,10 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); + RowContentBindParams params = new RowContentBindParams(); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + // Summary will be suppressed because there is only one child. mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); @@ -160,8 +175,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(); mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); @@ -178,15 +193,16 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(); mHeadsUpManager.showNotification(summaryEntry); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(true); - mNotificationEntryListener.onEntryReinflated(childEntry); + // Child entry finishes its inflation. + ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class); + verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); + callbackCaptor.getValue().onBindFinished(childEntry); // Alert is immediately removed from summary, and we show child as its content is inflated. assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey())); @@ -199,8 +215,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + NotificationEntry childEntry2 = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); mHeadsUpManager.showNotification(summaryEntry); @@ -214,9 +231,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupManager.onEntryAdded(childEntry2); // Child entry finishes its inflation. - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(true); - mNotificationEntryListener.onEntryReinflated(childEntry); + ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class); + verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); + callbackCaptor.getValue().onBindFinished(childEntry); verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager .getContentFlag()); @@ -229,8 +246,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); @@ -247,8 +265,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); @@ -270,8 +289,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47); - when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) - .thenReturn(false); + RowContentBindParams params = new RowContentBindParams(); + when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params); + mHeadsUpManager.showNotification(summaryEntry); // Trigger a transfer of alert state from summary to child. mGroupManager.onEntryAdded(summaryEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java index 54dc728e0c8b..d405fea78170 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -87,7 +86,6 @@ public final class NotificationGroupTestHelper { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); entry.setRow(row); when(row.getEntry()).thenReturn(entry); - when(row.isInflationFlagSet(anyInt())).thenReturn(true); return entry; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 1f37ad8b2b1c..5fb71599e2a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -55,7 +55,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.doze.DozeLog; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.KeyguardAffordanceView; @@ -133,8 +132,6 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private DynamicPrivacyController mDynamicPrivacyController; @Mock - private PluginManager mPluginManager; - @Mock private ShadeController mShadeController; @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager; @@ -218,7 +215,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { mNotificationPanelViewController = new NotificationPanelViewController(mView, mInjectionInflationController, coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController, - mFalsingManager, mPluginManager, mShadeController, + mFalsingManager, mShadeController, mNotificationLockscreenUserManager, mNotificationEntryManager, mKeyguardStateController, mStatusBarStateController, mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index fea4b8bbbb04..50276106f8d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -61,7 +61,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; @@ -70,6 +69,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; 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 dd896be0e120..b9d2d229cd69 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 @@ -54,7 +54,6 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -113,10 +112,9 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { notificationShadeWindowView, mock(NotificationListContainerViewGroup.class), mock(DozeScrimController.class), mock(ScrimController.class), mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class), - mock(NotificationAlertingManager.class), - mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class), - mock(KeyguardIndicationController.class), - mStatusBar, mock(ShadeControllerImpl.class), mCommandQueue, mInitController); + mock(NotificationAlertingManager.class), mock(KeyguardStateController.class), + mock(KeyguardIndicationController.class), mStatusBar, + mock(ShadeControllerImpl.class), mCommandQueue, mInitController); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index e90e398ba061..db17a6e106b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -95,7 +95,6 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationListener; @@ -120,8 +119,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; -import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -133,10 +131,8 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.volume.VolumeComponent; @@ -149,7 +145,6 @@ import org.mockito.MockitoAnnotations; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Optional; import javax.inject.Provider; @@ -166,7 +161,7 @@ public class StatusBarTest extends SysuiTestCase { private PowerManager mPowerManager; private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider; - @Mock private FeatureFlags mFeatureFlags; + @Mock private NotificationsController mNotificationsController; @Mock private LightBarController mLightBarController; @Mock private StatusBarIconController mStatusBarIconController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -180,7 +175,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private IDreamManager mDreamManager; @Mock private ScrimController mScrimController; @Mock private DozeScrimController mDozeScrimController; - @Mock private ArrayList<NotificationEntry> mNotificationList; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private BiometricUnlockController mBiometricUnlockController; @Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor; @@ -190,7 +184,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private RemoteInputController mRemoteInputController; - @Mock private RemoteInputUriController mRemoteInputUriController; @Mock private StatusBarStateControllerImpl mStatusBarStateController; @Mock private BatteryController mBatteryController; @Mock private DeviceProvisionedController mDeviceProvisionedController; @@ -214,8 +207,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private DynamicPrivacyController mDynamicPrivacyController; - @Mock private NotifPipelineInitializer mNewNotifPipeline; - @Mock private ZenModeController mZenModeController; @Mock private AutoHideController mAutoHideController; @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager; @Mock private UserSwitcherController mUserSwitcherController; @@ -223,7 +214,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private VibratorHelper mVibratorHelper; @Mock private BubbleController mBubbleController; @Mock private NotificationGroupManager mGroupManager; - @Mock private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationIconAreaController mNotificationIconAreaController; @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController; @@ -247,7 +237,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ViewMediatorCallback mViewMediatorCallback; @Mock private DismissCallbackRegistry mDismissCallbackRegistry; @Mock private ScreenPinningRequest mScreenPinningRequest; - @Mock private NotificationEntryManager mEntryManager; @Mock private LockscreenLockIconController mLockscreenLockIconController; @Mock private StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; @@ -256,7 +245,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private KeyguardDismissUtil mKeyguardDismissUtil; @Mock private ExtensionController mExtensionController; @Mock private UserInfoControllerImpl mUserInfoControllerImpl; - @Mock private NotificationRowBinderImpl mNotificationRowBinder; private ShadeController mShadeController; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private InitController mInitController = new InitController(); @@ -280,7 +268,7 @@ public class StatusBarTest extends SysuiTestCase { mMetricsLogger = new FakeMetricsLogger(); NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener, - mUiBgExecutor, mEntryManager, mStatusBarStateController, + mUiBgExecutor, mock(NotificationEntryManager.class), mStatusBarStateController, mExpansionStateLogger); notificationLogger.setVisibilityReporter(mock(Runnable.class)); @@ -334,7 +322,7 @@ public class StatusBarTest extends SysuiTestCase { mStatusBar = new StatusBar( mContext, - mFeatureFlags, + mNotificationsController, mLightBarController, mAutoHideController, mKeyguardUpdateMonitor, @@ -346,7 +334,6 @@ public class StatusBarTest extends SysuiTestCase { mHeadsUpManager, mDynamicPrivacyController, mBypassHeadsUpNotifier, - () -> mNewNotifPipeline, new FalsingManagerFake(), mBroadcastDispatcher, new RemoteInputQuickSettingsDisabler( @@ -356,7 +343,6 @@ public class StatusBarTest extends SysuiTestCase { ), mNotificationGutsManager, notificationLogger, - mEntryManager, mNotificationInterruptionStateProvider, mNotificationViewHierarchyManager, mKeyguardViewMediator, @@ -377,12 +363,10 @@ public class StatusBarTest extends SysuiTestCase { mVibratorHelper, mBubbleController, mGroupManager, - mGroupAlertTransferHelper, mVisualStabilityManager, mDeviceProvisionedController, mNavigationBarController, () -> mAssistManager, - mNotificationListener, configurationController, mNotificationShadeWindowController, mLockscreenLockIconController, @@ -399,7 +383,6 @@ public class StatusBarTest extends SysuiTestCase { Optional.of(mRecents), mStatusBarComponentBuilderProvider, mPluginManager, - mRemoteInputUriController, Optional.of(mDivider), mLightsOutNotifController, mStatusBarNotificationActivityStarterBuilder, @@ -414,7 +397,6 @@ public class StatusBarTest extends SysuiTestCase { mKeyguardDismissUtil, mExtensionController, mUserInfoControllerImpl, - mNotificationRowBinder, mDismissCallbackRegistry); when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn( @@ -675,7 +657,7 @@ public class StatusBarTest extends SysuiTestCase { public void testPanelOpenForHeadsUp() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); + when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true); mStatusBar.setBarStateForTest(StatusBarState.SHADE); @@ -693,7 +675,7 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testPanelOpenAndClear() { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); + when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); mStatusBar.setBarStateForTest(StatusBarState.SHADE); @@ -712,7 +694,7 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testPanelOpenAndNoClear() { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); + when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java index 631c580a490d..cd91f22bb753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -24,10 +26,12 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -import android.net.ConnectivityManager; +import android.net.TetheringManager; import android.net.wifi.WifiManager; import android.os.Handler; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -38,11 +42,14 @@ import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; +import java.util.Collections; import java.util.concurrent.Executor; @SmallTest @@ -51,13 +58,19 @@ import java.util.concurrent.Executor; public class HotspotControllerImplTest extends SysuiTestCase { @Mock - private ConnectivityManager mConnectivityManager; + private TetheringManager mTetheringManager; @Mock private WifiManager mWifiManager; @Mock + private UserManager mUserManager; + @Mock private HotspotController.Callback mCallback1; @Mock private HotspotController.Callback mCallback2; + @Mock + private TetheringManager.TetheringInterfaceRegexps mTetheringInterfaceRegexps; + @Captor + private ArgumentCaptor<TetheringManager.TetheringEventCallback> mTetheringCallbackCaptor; private HotspotControllerImpl mController; private TestableLooper mLooper; @@ -66,8 +79,13 @@ public class HotspotControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mLooper = TestableLooper.get(this); - mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager); mContext.addMockSystemService(WifiManager.class, mWifiManager); + mContext.addMockSystemService(TetheringManager.class, mTetheringManager); + mContext.addMockSystemService(UserManager.class, mUserManager); + + when(mUserManager.isUserAdmin(anyInt())).thenReturn(true); + when(mTetheringInterfaceRegexps.getTetherableWifiRegexs()).thenReturn( + Collections.singletonList("test")); doAnswer((InvocationOnMock invocation) -> { ((WifiManager.SoftApCallback) invocation.getArgument(1)) @@ -76,7 +94,11 @@ public class HotspotControllerImplTest extends SysuiTestCase { }).when(mWifiManager).registerSoftApCallback(any(Executor.class), any(WifiManager.SoftApCallback.class)); - mController = new HotspotControllerImpl(mContext, new Handler(mLooper.getLooper())); + Handler handler = new Handler(mLooper.getLooper()); + + mController = new HotspotControllerImpl(mContext, handler, handler); + verify(mTetheringManager) + .registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture()); } @Test @@ -117,4 +139,28 @@ public class HotspotControllerImplTest extends SysuiTestCase { verify(mWifiManager, never()).unregisterSoftApCallback(any()); } + @Test + public void testDefault_hotspotNotSupported() { + assertFalse(mController.isHotspotSupported()); + } + + @Test + public void testHotspotSupported_rightConditions() { + mTetheringCallbackCaptor.getValue().onTetheringSupported(true); + mTetheringCallbackCaptor.getValue() + .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps); + + assertTrue(mController.isHotspotSupported()); + } + + @Test + public void testHotspotSupported_callbackCalledOnChange() { + mController.addCallback(mCallback1); + mTetheringCallbackCaptor.getValue().onTetheringSupported(true); + mTetheringCallbackCaptor.getValue() + .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps); + + verify(mCallback1).onHotspotAvailabilityChanged(true); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 390e812b3613..df622542e22c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -39,9 +39,9 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.util.Assert; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt new file mode 100644 index 000000000000..8eecde1f4f7c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt @@ -0,0 +1,218 @@ +package com.android.systemui.util + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FloatingContentCoordinatorTest : SysuiTestCase() { + + private val screenBounds = Rect(0, 0, 1000, 1000) + + private val rect100px = Rect() + private val rect100pxFloating = FloatingRect(rect100px) + + private val rect200px = Rect() + private val rect200pxFloating = FloatingRect(rect200px) + + private val rect300px = Rect() + private val rect300pxFloating = FloatingRect(rect300px) + + private val floatingCoordinator = FloatingContentCoordinator() + + @Before + fun setup() { + rect100px.set(0, 0, 100, 100) + rect200px.set(0, 0, 200, 200) + rect300px.set(0, 0, 300, 300) + } + + @After + fun tearDown() { + // We need to remove this stuff since it's a singleton object and it'll be there for the + // next test. + floatingCoordinator.onContentRemoved(rect100pxFloating) + floatingCoordinator.onContentRemoved(rect200pxFloating) + floatingCoordinator.onContentRemoved(rect300pxFloating) + } + + @Test + fun testOnContentAdded() { + // Add rect1, and verify that the coordinator didn't move it. + floatingCoordinator.onContentAdded(rect100pxFloating) + assertEquals(rect100px.top, 0) + + // Add rect2, which intersects rect1. Verify that rect2 was not moved, since newly added + // content is allowed to remain where it is. rect1 should have been moved below rect2 + // since it was in the way. + floatingCoordinator.onContentAdded(rect200pxFloating) + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + verifyRectSizes() + } + + @Test + fun testOnContentRemoved() { + // Add rect1, and remove it. Then add rect2. Since rect1 was removed before that, it should + // no longer be considered in the way, so it shouldn't move when rect2 is added. + floatingCoordinator.onContentAdded(rect100pxFloating) + floatingCoordinator.onContentRemoved(rect100pxFloating) + floatingCoordinator.onContentAdded(rect200pxFloating) + + assertEquals(rect100px.top, 0) + assertEquals(rect200px.top, 0) + + verifyRectSizes() + } + + @Test + fun testOnContentMoved_twoRects() { + // Add rect1, which is at y = 0. + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Move rect2 down to 500px, where it won't conflict with rect1. + rect200px.offsetTo(0, 500) + floatingCoordinator.onContentAdded(rect200pxFloating) + + // Then, move it to 0px where it will absolutely conflict with rect1. + rect200px.offsetTo(0, 0) + floatingCoordinator.onContentMoved(rect200pxFloating) + + // The coordinator should have left rect2 alone, and moved rect1 below it. rect1 should now + // be at y = 200. + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + verifyRectSizes() + + // Move rect2 to y = 275px. Since this puts it at the bottom half of rect1, it should push + // rect1 upward and leave rect2 alone. + rect200px.offsetTo(0, 275) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertEquals(rect200px.top, 275) + assertEquals(rect100px.top, 175) + + verifyRectSizes() + + // Move rect2 to y = 110px. This makes it intersect rect1 again, but above its center of + // mass. That means rect1 should be pushed downward. + rect200px.offsetTo(0, 110) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertEquals(rect200px.top, 110) + assertEquals(rect100px.top, 310) + + verifyRectSizes() + } + + @Test + fun testOnContentMoved_threeRects() { + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Add rect2, which should displace rect1 to y = 200 + floatingCoordinator.onContentAdded(rect200pxFloating) + assertEquals(rect200px.top, 0) + assertEquals(rect100px.top, 200) + + // Add rect3, which should completely cover both rect1 and rect2. That should cause them to + // move away. The order in which they do so is non-deterministic, so just make sure none of + // the three Rects intersect. + floatingCoordinator.onContentAdded(rect300pxFloating) + + assertFalse(Rect.intersects(rect100px, rect200px)) + assertFalse(Rect.intersects(rect100px, rect300px)) + assertFalse(Rect.intersects(rect200px, rect300px)) + + // Move rect2 to intersect both rect1 and rect3. + rect200px.offsetTo(0, 150) + floatingCoordinator.onContentMoved(rect200pxFloating) + + assertFalse(Rect.intersects(rect100px, rect200px)) + assertFalse(Rect.intersects(rect100px, rect300px)) + assertFalse(Rect.intersects(rect200px, rect300px)) + } + + @Test + fun testOnContentMoved_respectsUpperBounds() { + // Add rect1, which is at y = 0. + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Move rect2 down to 500px, where it won't conflict with rect1. + rect200px.offsetTo(0, 500) + floatingCoordinator.onContentAdded(rect200pxFloating) + + // Then, move it to 90px where it will conflict with rect1, but with a center of mass below + // that of rect1's. This would normally mean that rect1 moves upward. However, since it's at + // the top of the screen, it should go downward instead. + rect200px.offsetTo(0, 90) + floatingCoordinator.onContentMoved(rect200pxFloating) + + // rect2 should have been left alone, rect1 is now below rect2 at y = 290px even though it + // was intersected from below. + assertEquals(rect200px.top, 90) + assertEquals(rect100px.top, 290) + } + + @Test + fun testOnContentMoved_respectsLowerBounds() { + // Put rect1 at the bottom of the screen and add it. + rect100px.offsetTo(0, screenBounds.bottom - 100) + floatingCoordinator.onContentAdded(rect100pxFloating) + + // Put rect2 at the bottom as well. Since its center of mass is above rect1's, rect1 would + // normally move downward. Since it's at the bottom of the screen, it should go upward + // instead. + rect200px.offsetTo(0, 800) + floatingCoordinator.onContentAdded(rect200pxFloating) + + assertEquals(rect200px.top, 800) + assertEquals(rect100px.top, 700) + } + + /** + * Tests that the rect sizes didn't change when the coordinator manipulated them. This allows us + * to assert only the value of rect.top in tests, since if top, width, and height are correct, + * that means top/left/right/bottom are all correct. + */ + private fun verifyRectSizes() { + assertEquals(100, rect100px.width()) + assertEquals(200, rect200px.width()) + assertEquals(300, rect300px.width()) + + assertEquals(100, rect100px.height()) + assertEquals(200, rect200px.height()) + assertEquals(300, rect300px.height()) + } + + /** + * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a + * Rect when needed. + */ + inner class FloatingRect( + private val underlyingRect: Rect + ) : FloatingContentCoordinator.FloatingContent { + override fun moveToBounds(bounds: Rect) { + underlyingRect.set(bounds) + } + + override fun getAllowedFloatingBoundsRegion(): Rect { + return screenBounds + } + + override fun getFloatingBoundsOnScreen(): Rect { + return underlyingRect + } + } +}
\ No newline at end of file diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index cf2b1f06d769..4efe93439b42 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -28,6 +28,7 @@ java_defaults { "netd_aidl_interface-unstable-java", "netlink-client", "networkstack-aidl-interfaces-unstable-java", + "android.hardware.tetheroffload.config-V1.0-java", "android.hardware.tetheroffload.control-V1.0-java", "net-utils-framework-common", ], @@ -48,26 +49,26 @@ android_library { // Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK). cc_library { name: "libtetherutilsjni", + sdk_version: "current", srcs: [ "jni/android_net_util_TetheringUtils.cpp", ], shared_libs: [ - "libcgrouprc", - "libnativehelper_compat_libc++", - "libvndksupport", - ], - static_libs: [ - "android.hardware.tetheroffload.config@1.0", "liblog", - "libbase", - "libbinderthreadstate", - "libcutils", - "libhidlbase", - "libjsoncpp", - "libprocessgroup", - "libutils", + "libnativehelper_compat_libc++", ], + // We cannot use plain "libc++" here to link libc++ dynamically because it results in: + // java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found + // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't + // build because soong complains of: + // module Tethering missing dependencies: libc++_shared + // + // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries + // we depend on do not dynamically link libc++. This is currently the case, because liblog is + // C-only and libnativehelper_compat_libc also uses stl: "c++_static". + stl: "c++_static", + cflags: [ "-Wall", "-Werror", @@ -86,9 +87,8 @@ java_defaults { // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies // explicitly. jni_libs: [ - "libcgrouprc", + "liblog", "libnativehelper_compat_libc++", - "libvndksupport", "libtetherutilsjni", ], resource_dirs: [ diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp index e0adb34dad6c..8c4f733fb62a 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -59,16 +59,33 @@ java_library { ], hostdex: true, // for hiddenapi check - visibility: [ - "//frameworks/base/packages/Tethering:__subpackages__", - //TODO(b/147200698) remove below lines when the platform is built with stubs - "//frameworks/base", - "//frameworks/base/services", - "//frameworks/base/services/core", - ], + visibility: ["//frameworks/base/packages/Tethering:__subpackages__"], apex_available: ["com.android.tethering"], } +droidstubs { + name: "framework-tethering-stubs-sources", + defaults: ["framework-module-stubs-defaults-module_libs_api"], + srcs: [ + "src/android/net/TetheredClient.java", + "src/android/net/TetheringManager.java", + "src/android/net/TetheringConstants.java", + ], + libs: [ + "tethering-aidl-interfaces-java", + "framework-all", + ], + sdk_version: "core_platform", +} + +java_library { + name: "framework-tethering-stubs", + srcs: [":framework-tethering-stubs-sources"], + libs: ["framework-all"], + static_libs: ["tethering-aidl-interfaces-java"], + sdk_version: "core_platform", +} + filegroup { name: "framework-tethering-srcs", srcs: [ diff --git a/packages/Tethering/common/TetheringLib/jarjar-rules.txt b/packages/Tethering/common/TetheringLib/jarjar-rules.txt index 35e0f88e70fa..1403bba3445a 100644 --- a/packages/Tethering/common/TetheringLib/jarjar-rules.txt +++ b/packages/Tethering/common/TetheringLib/jarjar-rules.txt @@ -1 +1,2 @@ rule android.annotation.** com.android.networkstack.tethering.annotation.@1 +rule com.android.internal.annotations.** com.android.networkstack.tethering.annotation.@1
\ No newline at end of file diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java index 651468846ca8..ca5ef09d6ad9 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java @@ -16,6 +16,8 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -34,6 +36,7 @@ import java.util.Objects; * @hide */ @SystemApi +@SystemApi(client = MODULE_LIBRARIES) @TestApi public final class TetheredClient implements Parcelable { @NonNull diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java index 00cf98e0fc2d..df87ac994d42 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java @@ -16,6 +16,9 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.SystemApi; import android.os.ResultReceiver; /** @@ -28,39 +31,30 @@ import android.os.ResultReceiver; * symbols from framework-tethering even when they are in a non-hidden class. * @hide */ +@SystemApi(client = MODULE_LIBRARIES) public class TetheringConstants { /** * Extra used for communicating with the TetherService. Includes the type of tethering to * enable if any. - * - * {@hide} */ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; /** * Extra used for communicating with the TetherService. Includes the type of tethering for * which to cancel provisioning. - * - * {@hide} */ public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; /** * Extra used for communicating with the TetherService. True to schedule a recheck of tether * provisioning. - * - * {@hide} */ public static final String EXTRA_SET_ALARM = "extraSetAlarm"; /** * Tells the TetherService to run a provision check now. - * - * {@hide} */ public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; /** * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} * which will receive provisioning results. Can be left empty. - * - * {@hide} */ public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 37ce1d57da36..6a9f010449c4 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -15,6 +15,8 @@ */ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +32,9 @@ import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -37,6 +42,7 @@ import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Supplier; /** * This class provides the APIs to control the tethering service. @@ -46,21 +52,28 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi +@SystemApi(client = MODULE_LIBRARIES) @TestApi public class TetheringManager { private static final String TAG = TetheringManager.class.getSimpleName(); private static final int DEFAULT_TIMEOUT_MS = 60_000; + private static final long CONNECTOR_POLL_INTERVAL_MILLIS = 200L; - private static TetheringManager sInstance; + @GuardedBy("mConnectorWaitQueue") + @Nullable + private ITetheringConnector mConnector; + @GuardedBy("mConnectorWaitQueue") + @NonNull + private final List<ConnectorConsumer> mConnectorWaitQueue = new ArrayList<>(); + private final Supplier<IBinder> mConnectorSupplier; - private final ITetheringConnector mConnector; private final TetheringCallbackInternal mCallback; private final Context mContext; private final ArrayMap<TetheringEventCallback, ITetheringEventCallback> mTetheringEventCallbacks = new ArrayMap<>(); - private TetheringConfigurationParcel mTetheringConfiguration; - private TetherStatesParcel mTetherStatesParcel; + private volatile TetheringConfigurationParcel mTetheringConfiguration; + private volatile TetherStatesParcel mTetherStatesParcel; /** * Broadcast Action: A tetherable connection has come or gone. @@ -162,29 +175,140 @@ public class TetheringManager { /** * Create a TetheringManager object for interacting with the tethering service. * + * @param context Context for the manager. + * @param connectorSupplier Supplier for the manager connector; may return null while the + * service is not connected. * {@hide} */ - public TetheringManager(@NonNull final Context context, @NonNull final IBinder service) { + @SystemApi(client = MODULE_LIBRARIES) + public TetheringManager(@NonNull final Context context, + @NonNull Supplier<IBinder> connectorSupplier) { mContext = context; - mConnector = ITetheringConnector.Stub.asInterface(service); mCallback = new TetheringCallbackInternal(); + mConnectorSupplier = connectorSupplier; final String pkgName = mContext.getOpPackageName(); + + final IBinder connector = mConnectorSupplier.get(); + // If the connector is available on start, do not start a polling thread. This introduces + // differences in the thread that sends the oneway binder calls to the service between the + // first few seconds after boot and later, but it avoids always having differences between + // the first usage of TetheringManager from a process and subsequent usages (so the + // difference is only on boot). On boot binder calls may be queued until the service comes + // up and be sent from a worker thread; later, they are always sent from the caller thread. + // Considering that it's just oneway binder calls, and ordering is preserved, this seems + // better than inconsistent behavior persisting after boot. + if (connector != null) { + mConnector = ITetheringConnector.Stub.asInterface(connector); + } else { + startPollingForConnector(); + } + Log.i(TAG, "registerTetheringEventCallback:" + pkgName); + getConnector(c -> c.registerTetheringEventCallback(mCallback, pkgName)); + } + + private void startPollingForConnector() { + new Thread(() -> { + while (true) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + // Not much to do here, the system needs to wait for the connector + } + + final IBinder connector = mConnectorSupplier.get(); + if (connector != null) { + onTetheringConnected(ITetheringConnector.Stub.asInterface(connector)); + return; + } + } + }).start(); + } + + private interface ConnectorConsumer { + void onConnectorAvailable(ITetheringConnector connector) throws RemoteException; + } + + private void onTetheringConnected(ITetheringConnector connector) { + // Process the connector wait queue in order, including any items that are added + // while processing. + // + // 1. Copy the queue to a local variable under lock. + // 2. Drain the local queue with the lock released (otherwise, enqueuing future commands + // would block on the lock). + // 3. Acquire the lock again. If any new tasks were queued during step 2, goto 1. + // If not, set mConnector to non-null so future tasks are run immediately, not queued. + // + // For this to work, all calls to the tethering service must use getConnector(), which + // ensures that tasks are added to the queue with the lock held. + // + // Once mConnector is set to non-null, it will never be null again. If the network stack + // process crashes, no recovery is possible. + // TODO: evaluate whether it is possible to recover from network stack process crashes + // (though in most cases the system will have crashed when the network stack process + // crashes). + do { + final List<ConnectorConsumer> localWaitQueue; + synchronized (mConnectorWaitQueue) { + localWaitQueue = new ArrayList<>(mConnectorWaitQueue); + mConnectorWaitQueue.clear(); + } + + // Allow more tasks to be added at the end without blocking while draining the queue. + for (ConnectorConsumer task : localWaitQueue) { + try { + task.onConnectorAvailable(connector); + } catch (RemoteException e) { + // Most likely the network stack process crashed, which is likely to crash the + // system. Keep processing other requests but report the error loudly. + Log.wtf(TAG, "Error processing request for the tethering connector", e); + } + } + + synchronized (mConnectorWaitQueue) { + if (mConnectorWaitQueue.size() == 0) { + mConnector = connector; + return; + } + } + } while (true); + } + + /** + * Asynchronously get the ITetheringConnector to execute some operation. + * + * <p>If the connector is already available, the operation will be executed on the caller's + * thread. Otherwise it will be queued and executed on a worker thread. The operation should be + * limited to performing oneway binder calls to minimize differences due to threading. + */ + private void getConnector(ConnectorConsumer consumer) { + final ITetheringConnector connector; + synchronized (mConnectorWaitQueue) { + connector = mConnector; + if (connector == null) { + mConnectorWaitQueue.add(consumer); + return; + } + } + try { - mConnector.registerTetheringEventCallback(mCallback, pkgName); + consumer.onConnectorAvailable(connector); } catch (RemoteException e) { throw new IllegalStateException(e); } } private interface RequestHelper { - void runRequest(IIntResultListener listener); + void runRequest(ITetheringConnector connector, IIntResultListener listener); } + // Used to dispatch legacy ConnectivityManager methods that expect tethering to be able to + // return results and perform operations synchronously. + // TODO: remove once there are no callers of these legacy methods. private class RequestDispatcher { private final ConditionVariable mWaiting; - public int mRemoteResult; + public volatile int mRemoteResult; private final IIntResultListener mListener = new IIntResultListener.Stub() { @Override @@ -199,7 +323,7 @@ public class TetheringManager { } int waitForResult(final RequestHelper request) { - request.runRequest(mListener); + getConnector(c -> request.runRequest(c, mListener)); if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) { throw new IllegalStateException("Callback timeout"); } @@ -222,7 +346,7 @@ public class TetheringManager { } private class TetheringCallbackInternal extends ITetheringEventCallback.Stub { - private int mError = TETHER_ERROR_NO_ERROR; + private volatile int mError = TETHER_ERROR_NO_ERROR; private final ConditionVariable mWaitForCallback = new ConditionVariable(); @Override @@ -275,14 +399,15 @@ public class TetheringManager { * {@hide} */ @Deprecated + @SystemApi(client = MODULE_LIBRARIES) public int tether(@NonNull final String iface) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "tether caller:" + callerPkg); final RequestDispatcher dispatcher = new RequestDispatcher(); - return dispatcher.waitForResult(listener -> { + return dispatcher.waitForResult((connector, listener) -> { try { - mConnector.tether(iface, callerPkg, listener); + connector.tether(iface, callerPkg, listener); } catch (RemoteException e) { throw new IllegalStateException(e); } @@ -298,15 +423,16 @@ public class TetheringManager { * {@hide} */ @Deprecated + @SystemApi(client = MODULE_LIBRARIES) public int untether(@NonNull final String iface) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "untether caller:" + callerPkg); final RequestDispatcher dispatcher = new RequestDispatcher(); - return dispatcher.waitForResult(listener -> { + return dispatcher.waitForResult((connector, listener) -> { try { - mConnector.untether(iface, callerPkg, listener); + connector.untether(iface, callerPkg, listener); } catch (RemoteException e) { throw new IllegalStateException(e); } @@ -324,15 +450,16 @@ public class TetheringManager { * {@hide} */ @Deprecated + @SystemApi(client = MODULE_LIBRARIES) public int setUsbTethering(final boolean enable) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "setUsbTethering caller:" + callerPkg); final RequestDispatcher dispatcher = new RequestDispatcher(); - return dispatcher.waitForResult(listener -> { + return dispatcher.waitForResult((connector, listener) -> { try { - mConnector.setUsbTethering(enable, callerPkg, listener); + connector.setUsbTethering(enable, callerPkg, listener); } catch (RemoteException e) { throw new IllegalStateException(e); } @@ -467,11 +594,7 @@ public class TetheringManager { }); } }; - try { - mConnector.startTethering(request.getParcel(), callerPkg, listener); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } + getConnector(c -> c.startTethering(request.getParcel(), callerPkg, listener)); } /** @@ -509,15 +632,15 @@ public class TetheringManager { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "stopTethering caller:" + callerPkg); - final RequestDispatcher dispatcher = new RequestDispatcher(); - - dispatcher.waitForResult(listener -> { - try { - mConnector.stopTethering(type, callerPkg, listener); - } catch (RemoteException e) { - throw new IllegalStateException(e); + getConnector(c -> c.stopTethering(type, callerPkg, new IIntResultListener.Stub() { + @Override + public void onResult(int resultCode) { + // TODO: provide an API to obtain result + // This has never been possible as stopTethering has always been void and never + // taken a callback object. The only indication that callers have is if the call + // results in a TETHER_STATE_CHANGE broadcast. } - }); + })); } /** @@ -586,17 +709,14 @@ public class TetheringManager { * {@hide} */ // TODO: improve the usage of ResultReceiver, b/145096122 + @SystemApi(client = MODULE_LIBRARIES) public void requestLatestTetheringEntitlementResult(final int type, @NonNull final ResultReceiver receiver, final boolean showEntitlementUi) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "getLatestTetheringEntitlementResult caller:" + callerPkg); - try { - mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi, - callerPkg); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } + getConnector(c -> c.requestLatestTetheringEntitlementResult( + type, receiver, showEntitlementUi, callerPkg)); } /** @@ -832,11 +952,7 @@ public class TetheringManager { }); } }; - try { - mConnector.registerTetheringEventCallback(remoteCallback, callerPkg); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } + getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg)); mTetheringEventCallbacks.put(callback, remoteCallback); } } @@ -860,11 +976,8 @@ public class TetheringManager { if (remoteCallback == null) { throw new IllegalArgumentException("callback was not registered."); } - try { - mConnector.unregisterTetheringEventCallback(remoteCallback, callerPkg); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } + + getConnector(c -> c.unregisterTetheringEventCallback(remoteCallback, callerPkg)); } } @@ -877,6 +990,7 @@ public class TetheringManager { * interface * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public int getLastTetherError(@NonNull final String iface) { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR; @@ -899,6 +1013,7 @@ public class TetheringManager { * what interfaces are considered tetherable usb interfaces. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableUsbRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableUsbRegexs; @@ -913,6 +1028,7 @@ public class TetheringManager { * what interfaces are considered tetherable wifi interfaces. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableWifiRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableWifiRegexs; @@ -927,6 +1043,7 @@ public class TetheringManager { * what interfaces are considered tetherable bluetooth interfaces. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableBluetoothRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableBluetoothRegexs; @@ -939,6 +1056,7 @@ public class TetheringManager { * @return an array of 0 or more Strings of tetherable interface names. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; @@ -952,6 +1070,7 @@ public class TetheringManager { * @return an array of 0 or more String of currently tethered interface names. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetheredIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; @@ -971,6 +1090,7 @@ public class TetheringManager { * which failed to tether. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetheringErroredIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; @@ -998,13 +1118,14 @@ public class TetheringManager { * @return a boolean - {@code true} indicating Tethering is supported. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public boolean isTetheringSupported() { final String callerPkg = mContext.getOpPackageName(); final RequestDispatcher dispatcher = new RequestDispatcher(); - final int ret = dispatcher.waitForResult(listener -> { + final int ret = dispatcher.waitForResult((connector, listener) -> { try { - mConnector.isTetheringSupported(callerPkg, listener); + connector.isTetheringSupported(callerPkg, listener); } catch (RemoteException e) { throw new IllegalStateException(e); } @@ -1027,13 +1148,14 @@ public class TetheringManager { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "stopAllTethering caller:" + callerPkg); - final RequestDispatcher dispatcher = new RequestDispatcher(); - dispatcher.waitForResult(listener -> { - try { - mConnector.stopAllTethering(callerPkg, listener); - } catch (RemoteException e) { - throw new IllegalStateException(e); + getConnector(c -> c.stopAllTethering(callerPkg, new IIntResultListener.Stub() { + @Override + public void onResult(int resultCode) { + // TODO: add an API parameter to send result to caller. + // This has never been possible as stopAllTethering has always been void and never + // taken a callback object. The only indication that callers have is if the call + // results in a TETHER_STATE_CHANGE broadcast. } - }); + })); } } diff --git a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp index 1cf8f988432c..549344064405 100644 --- a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp +++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp @@ -16,123 +16,18 @@ #include <errno.h> #include <error.h> -#include <hidl/HidlSupport.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> -#include <linux/netfilter/nfnetlink.h> -#include <linux/netlink.h> #include <net/if.h> #include <netinet/icmp6.h> #include <sys/socket.h> -#include <android-base/unique_fd.h> -#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h> #define LOG_TAG "TetheringUtils" -#include <utils/Log.h> +#include <android/log.h> namespace android { -using hardware::hidl_handle; -using hardware::hidl_string; -using hardware::tetheroffload::config::V1_0::IOffloadConfig; - -namespace { - -inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) { - return reinterpret_cast<const sockaddr *>(nladdr); -} - -int conntrackSocket(unsigned groups) { - base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)); - if (s.get() < 0) return -errno; - - const struct sockaddr_nl bind_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) { - return -errno; - } - - const struct sockaddr_nl kernel_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) { - return -errno; - } - - return s.release(); -} - -// Return a hidl_handle that owns the file descriptor owned by fd, and will -// auto-close it (otherwise there would be double-close problems). -// -// Rely upon the compiler to eliminate the constexprs used for clarity. -hidl_handle handleFromFileDescriptor(base::unique_fd fd) { - hidl_handle h; - - static constexpr int kNumFds = 1; - static constexpr int kNumInts = 0; - native_handle_t *nh = native_handle_create(kNumFds, kNumInts); - nh->data[0] = fd.release(); - - static constexpr bool kTakeOwnership = true; - h.setTo(nh, kTakeOwnership); - - return h; -} - -} // namespace - -static jboolean android_net_util_configOffload( - JNIEnv* /* env */) { - sp<IOffloadConfig> configInterface = IOffloadConfig::getService(); - if (configInterface.get() == nullptr) { - ALOGD("Could not find IOffloadConfig service."); - return false; - } - - // Per the IConfigOffload definition: - // - // fd1 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). - // - // fd2 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). - base::unique_fd - fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)), - fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY)); - if (fd1.get() < 0 || fd2.get() < 0) { - ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno)); - return false; - } - - hidl_handle h1(handleFromFileDescriptor(std::move(fd1))), - h2(handleFromFileDescriptor(std::move(fd2))); - - bool rval(false); - hidl_string msg; - const auto status = configInterface->setHandles(h1, h2, - [&rval, &msg](bool success, const hidl_string& errMsg) { - rval = success; - msg = errMsg; - }); - if (!status.isOk() || !rval) { - ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'", - status.description().c_str(), msg.c_str()); - // If status is somehow not ok, make sure rval captures this too. - rval = false; - } - - return rval; -} - static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd, jint ifIndex) { @@ -229,7 +124,6 @@ static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject j */ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - { "configOffload", "()Z", (void*) android_net_util_configOffload }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket }, }; @@ -242,7 +136,7 @@ int register_android_net_util_TetheringUtils(JNIEnv* env) { extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - ALOGE("ERROR: GetEnv failed"); + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed"); return JNI_ERR; } diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index c489cbcd5a1c..4af5c5371952 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -47,6 +47,7 @@ should be empty. An example would be "p2p-p2p\\d-.*" --> <string-array translatable="false" name="config_tether_wifi_p2p_regexs"> <item>"p2p-p2p\\d-.*"</item> + <item>"p2p\\d"</item> </string-array> <!-- List of regexpressions describing the interface (if any) that represent tetherable diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index fa543bdce735..5a6d5c1cbfb0 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -24,14 +24,6 @@ import java.net.SocketException; * {@hide} */ public class TetheringUtils { - - /** - * Offload management process need to know conntrack rules to support NAT, but it may not have - * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and - * share them with offload management process. - */ - public static native boolean configOffload(); - /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 90b9d3f148dc..b54571720857 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -18,19 +18,28 @@ package com.android.server.connectivity.tethering; import static android.net.util.TetheringUtils.uint16; +import android.hardware.tetheroffload.config.V1_0.IOffloadConfig; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.net.netlink.NetlinkSocket; import android.net.util.SharedLog; -import android.net.util.TetheringUtils; +import android.net.util.SocketUtils; import android.os.Handler; +import android.os.NativeHandle; import android.os.RemoteException; +import android.system.ErrnoException; +import android.system.Os; import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketAddress; +import java.net.SocketException; import java.util.ArrayList; @@ -49,6 +58,10 @@ public class OffloadHardwareInterface { private static final String NO_INTERFACE_NAME = ""; private static final String NO_IPV4_ADDRESS = ""; private static final String NO_IPV4_GATEWAY = ""; + // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h + private static final int NF_NETLINK_CONNTRACK_NEW = 1; + private static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + private static final int NF_NETLINK_CONNTRACK_DESTROY = 4; private final Handler mHandler; private final SharedLog mLog; @@ -121,9 +134,103 @@ public class OffloadHardwareInterface { return DEFAULT_TETHER_OFFLOAD_DISABLED; } - /** Configure offload management process. */ + /** + * Offload management process need to know conntrack rules to support NAT, but it may not have + * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and + * share them with offload management process. + */ public boolean initOffloadConfig() { - return TetheringUtils.configOffload(); + IOffloadConfig offloadConfig; + try { + offloadConfig = IOffloadConfig.getService(); + } catch (RemoteException e) { + mLog.e("getIOffloadConfig error " + e); + return false; + } + if (offloadConfig == null) { + mLog.e("Could not find IOffloadConfig service"); + return false; + } + // Per the IConfigOffload definition: + // + // h1 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). + // + // h2 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). + final NativeHandle h1 = createConntrackSocket( + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); + if (h1 == null) return false; + + final NativeHandle h2 = createConntrackSocket( + NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); + if (h2 == null) { + closeFdInNativeHandle(h1); + return false; + } + + final CbResults results = new CbResults(); + try { + offloadConfig.setHandles(h1, h2, + (boolean success, String errMsg) -> { + results.mSuccess = success; + results.mErrMsg = errMsg; + }); + } catch (RemoteException e) { + record("initOffloadConfig, setHandles fail", e); + return false; + } + // Explicitly close FDs. + closeFdInNativeHandle(h1); + closeFdInNativeHandle(h2); + + record("initOffloadConfig, setHandles results:", results); + return results.mSuccess; + } + + private void closeFdInNativeHandle(final NativeHandle h) { + try { + h.close(); + } catch (IOException | IllegalStateException e) { + // IllegalStateException means fd is already closed, do nothing here. + // Also nothing we can do if IOException. + } + } + + private NativeHandle createConntrackSocket(final int groups) { + FileDescriptor fd; + try { + fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER); + } catch (ErrnoException e) { + mLog.e("Unable to create conntrack socket " + e); + return null; + } + + final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); + try { + Os.bind(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + try { + Os.connect(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("connect to kernel fail for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + + return new NativeHandle(fd, true); } /** Initialize the tethering offload HAL. */ diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 07abe1adeb52..64c16e41dbf5 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -272,13 +272,6 @@ public class Tethering { mStateReceiver = new StateReceiver(); - mNetdCallback = new NetdCallback(); - try { - mNetd.registerUnsolicitedEventListener(mNetdCallback); - } catch (RemoteException e) { - mLog.e("Unable to register netd UnsolicitedEventListener"); - } - final UserManager userManager = (UserManager) mContext.getSystemService( Context.USER_SERVICE); mTetheringRestriction = new UserRestrictionActionListener(userManager, this); @@ -287,6 +280,14 @@ public class Tethering { // Load tethering configuration. updateConfiguration(); + // NetdCallback should be registered after updateConfiguration() to ensure + // TetheringConfiguration is created. + mNetdCallback = new NetdCallback(); + try { + mNetd.registerUnsolicitedEventListener(mNetdCallback); + } catch (RemoteException e) { + mLog.e("Unable to register netd UnsolicitedEventListener"); + } startStateMachineUpdaters(mHandler); startTrackDefaultNetwork(); @@ -1943,7 +1944,8 @@ public class Tethering { parcel.tetheringSupported = mDeps.isTetheringSupported(); parcel.upstreamNetwork = mTetherUpstream; parcel.config = mConfig.toStableParcelable(); - parcel.states = mTetherStatesParcel; + parcel.states = + mTetherStatesParcel != null ? mTetherStatesParcel : emptyTetherStatesParcel(); try { callback.onCallbackStarted(parcel); } catch (RemoteException e) { @@ -1952,6 +1954,17 @@ public class Tethering { }); } + private TetherStatesParcel emptyTetherStatesParcel() { + final TetherStatesParcel parcel = new TetherStatesParcel(); + parcel.availableList = new String[0]; + parcel.tetheredList = new String[0]; + parcel.localOnlyList = new String[0]; + parcel.erroredIfaceList = new String[0]; + parcel.lastErrorList = new int[0]; + + return parcel; + } + /** Unregister tethering event callback */ void unregisterTetheringEventCallback(ITetheringEventCallback callback) { mHandler.post(() -> { diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java index 7dc5c5f2db8a..020b32adcfd7 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java @@ -77,8 +77,8 @@ public class TetheringService extends Service { mLog.mark("onCreate"); mDeps = getTetheringDependencies(); mContext = mDeps.getContext(); - mTethering = makeTethering(mDeps); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mTethering = makeTethering(mDeps); } /** diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp index 13174c5bb57a..c6905ec8efce 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -34,7 +34,15 @@ android_test { "TetheringApiCurrentLib", "testables", ], + // TODO(b/147200698) change sdk_version to module-current and + // remove framework-minus-apex, ext, and framework-res + sdk_version: "core_platform", libs: [ + "framework-minus-apex", + "ext", + "framework-res", + "framework-wifi-stubs", + "framework-telephony-stubs", "android.test.runner", "android.test.base", "android.test.mock", diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 4710287f33f3..6d49e20e5753 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -127,6 +127,7 @@ import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.networkstack.tethering.R; +import com.android.testutils.MiscAssertsKt; import org.junit.After; import org.junit.Before; @@ -1220,6 +1221,16 @@ public class TetheringTest { } } + private void assertTetherStatesNotNullButEmpty(final TetherStatesParcel parcel) { + assertFalse(parcel == null); + assertEquals(0, parcel.availableList.length); + assertEquals(0, parcel.tetheredList.length); + assertEquals(0, parcel.localOnlyList.length); + assertEquals(0, parcel.erroredIfaceList.length); + assertEquals(0, parcel.lastErrorList.length); + MiscAssertsKt.assertFieldCountEquals(5, TetherStatesParcel.class); + } + @Test public void testRegisterTetheringEventCallback() throws Exception { TestTetheringEventCallback callback = new TestTetheringEventCallback(); @@ -1232,7 +1243,7 @@ public class TetheringTest { callback.expectConfigurationChanged( mTethering.getTetheringConfiguration().toStableParcelable()); TetherStatesParcel tetherState = callback.pollTetherStatesChanged(); - assertEquals(tetherState, null); + assertTetherStatesNotNullButEmpty(tetherState); // 2. Enable wifi tethering. UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState); diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index e4de6259e07d..693ca52b4ed1 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -34,6 +34,13 @@ </intent-filter> </activity> + <activity android:name=".PlatformVpnConfirmDialog" + android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" + android:noHistory="true" + android:excludeFromRecents="true" + android:exported="true"> + </activity> + <activity android:name=".ManageDialog" android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" android:noHistory="true" diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index 48adb9ba3f63..e66f2cc17a7f 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -23,6 +23,7 @@ import android.content.DialogInterface; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.net.IConnectivityManager; +import android.net.VpnManager; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; @@ -43,10 +44,20 @@ public class ConfirmDialog extends AlertActivity implements DialogInterface.OnClickListener, ImageGetter { private static final String TAG = "VpnConfirm"; + @VpnManager.VpnType private final int mVpnType; + private String mPackage; private IConnectivityManager mService; + public ConfirmDialog() { + this(VpnManager.TYPE_VPN_SERVICE); + } + + public ConfirmDialog(@VpnManager.VpnType int vpnType) { + mVpnType = vpnType; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -138,7 +149,7 @@ public class ConfirmDialog extends AlertActivity if (mService.prepareVpn(null, mPackage, UserHandle.myUserId())) { // Authorize this app to initiate VPN connections in the future without user // intervention. - mService.setVpnPackageAuthorization(mPackage, UserHandle.myUserId(), true); + mService.setVpnPackageAuthorization(mPackage, UserHandle.myUserId(), mVpnType); setResult(RESULT_OK); } } catch (Exception e) { diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/PlatformVpnConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/PlatformVpnConfirmDialog.java new file mode 100644 index 000000000000..e2e297caad14 --- /dev/null +++ b/packages/VpnDialogs/src/com/android/vpndialogs/PlatformVpnConfirmDialog.java @@ -0,0 +1,29 @@ +/* + * 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.vpndialogs; + +import android.net.VpnManager; + +/** + * PlatformVpnConfirmDialog is a minimal subclass for requesting user consent for platform VPN + * profiles. + */ +public class PlatformVpnConfirmDialog extends ConfirmDialog { + public PlatformVpnConfirmDialog() { + super(VpnManager.TYPE_VPN_PLATFORM); + } +} diff --git a/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java b/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java index 950107301af3..fcb113e90720 100644 --- a/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java +++ b/packages/WallpaperCropper/src/com/android/photos/views/TiledImageRenderer.java @@ -26,7 +26,6 @@ import android.util.Log; import android.util.Pools.Pool; import android.util.Pools.SynchronizedPool; import android.view.View; -import android.view.WindowManager; import com.android.gallery3d.common.Utils; import com.android.gallery3d.glrenderer.BasicTexture; @@ -164,9 +163,7 @@ public class TiledImageRenderer { private static boolean isHighResolution(Context context) { DisplayMetrics metrics = new DisplayMetrics(); - WindowManager wm = (WindowManager) - context.getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getMetrics(metrics); + context.getDisplay().getMetrics(metrics); return metrics.heightPixels > 2048 || metrics.widthPixels > 2048; } diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java index f878b4d11f72..6112da5cd13b 100644 --- a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java +++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java @@ -37,9 +37,9 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; +import android.util.Size; import android.view.Display; import android.view.View; -import android.view.WindowManager; import android.widget.Toast; import com.android.gallery3d.common.Utils; @@ -231,18 +231,18 @@ public class WallpaperCropActivity extends Activity { return x * aspectRatio + y; } - static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) { + static protected Point getDefaultWallpaperSize(Resources res, Display display) { if (sDefaultWallpaperSize == null) { Point minDims = new Point(); Point maxDims = new Point(); - windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims); + display.getCurrentSizeRange(minDims, maxDims); int maxDim = Math.max(maxDims.x, maxDims.y); int minDim = Math.max(minDims.x, minDims.y); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { Point realSize = new Point(); - windowManager.getDefaultDisplay().getRealSize(realSize); + display.getRealSize(realSize); maxDim = Math.max(realSize.x, realSize.y); minDim = Math.min(realSize.x, realSize.y); } @@ -331,8 +331,7 @@ public class WallpaperCropActivity extends Activity { // this device int rotation = getRotationFromExif(res, resId); Point inSize = mCropView.getSourceDimensions(); - Point outSize = getDefaultWallpaperSize(getResources(), - getWindowManager()); + Point outSize = getDefaultWallpaperSize(getResources(), getDisplay()); RectF crop = getMaxCropRect( inSize.x, inSize.y, outSize.x, outSize.y, false); Runnable onEndCrop = new Runnable() { @@ -359,14 +358,11 @@ public class WallpaperCropActivity extends Activity { // Get the crop boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; - Display d = getWindowManager().getDefaultDisplay(); - - Point displaySize = new Point(); - d.getSize(displaySize); - boolean isPortrait = displaySize.x < displaySize.y; + Size windowSize = getWindowManager().getCurrentWindowMetrics().getSize(); + boolean isPortrait = windowSize.getWidth() < windowSize.getHeight(); Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), - getWindowManager()); + getDisplay()); // Get the crop RectF cropRect = mCropView.getCrop(); diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk index 8a3ac9404408..dcdb80b497d0 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -26,7 +26,9 @@ LOCAL_REQUIRED_MODULES := \ AccentColorPurpleOverlay \ DisplayCutoutEmulationCornerOverlay \ DisplayCutoutEmulationDoubleOverlay \ + DisplayCutoutEmulationHoleOverlay \ DisplayCutoutEmulationTallOverlay \ + DisplayCutoutEmulationWaterfallOverlay \ FontNotoSerifSourceOverlay \ IconPackCircularAndroidOverlay \ IconPackCircularLauncherOverlay \ @@ -47,7 +49,6 @@ LOCAL_REQUIRED_MODULES := \ IconShapeSquircleOverlay \ IconShapeTeardropOverlay \ NavigationBarMode3ButtonOverlay \ - NavigationBarMode2ButtonOverlay \ NavigationBarModeGesturalOverlay \ NavigationBarModeGesturalOverlayNarrowBack \ NavigationBarModeGesturalOverlayWideBack \ diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationHoleOverlay/Android.mk new file mode 100644 index 000000000000..6d8fc2493506 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := DisplayCutoutEmulationHole + + +LOCAL_PRODUCT_MODULE := true + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := DisplayCutoutEmulationHoleOverlay +LOCAL_SDK_VERSION := current + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..9fd82abd6c8c --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/AndroidManifest.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.internal.display.cutout.emulation.hole" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" + android:category="com.android.internal.display_cutout_emulation" + android:priority="1"/> + + <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/> +</manifest> diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-land/config.xml new file mode 100644 index 000000000000..2e971ded2256 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-land/config.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<resources> + <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape --> + <dimen name="quick_qs_offset_height">28dp</dimen> + <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 --> + <dimen name="quick_qs_total_height">156dp</dimen> +</resources> diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml new file mode 100644 index 000000000000..9f558d0e2bd5 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml @@ -0,0 +1,61 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- The bounding path of the cutout region of the main built-in display. + Must either be empty if there is no cutout region, or a string that is parsable by + {@link android.util.PathParser}. + + The path is assumed to be specified in display coordinates with pixel units and in + the display's native orientation, with the origin of the coordinate system at the + center top of the display. + + To facilitate writing device-independent emulation overlays, the marker `@dp` can be + appended after the path string to interpret coordinates in dp instead of px units. + Note that a physical cutout should be configured in pixels for the best results. + --> + <!-- Display cutout configuration --> + <string translatable="false" name="config_mainBuiltInDisplayCutout"> + M 128,83 A 44,44 0 0 1 84,127 44,44 0 0 1 40,83 44,44 0 0 1 84,39 44,44 0 0 1 128,83 Z + @left + </string> + + <string translatable="false" name="config_mainBuiltInDisplayCutoutRectApproximation"> + M 0.0,0.0 + h 136 + v 136 + h -136 + Z + @left + </string> + + <!-- Whether the display cutout region of the main built-in display should be forced to + black in software (to avoid aliasing or emulate a cutout that is not physically existent). + --> + <bool name="config_fillMainBuiltInDisplayCutout">true</bool> + + <!-- Height of the status bar --> + <dimen name="status_bar_height_portrait">136px</dimen> + <dimen name="status_bar_height_landscape">28dp</dimen> + <!-- Height of area above QQS where battery/time go (equal to status bar) --> + <dimen name="quick_qs_offset_height">136px</dimen> + <!-- Total height of QQS (quick_qs_offset_height + 128) --> + <dimen name="quick_qs_total_height">488px</dimen> + +</resources> + + diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/strings.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/strings.xml new file mode 100644 index 000000000000..ab25695e7289 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <string name="display_cutout_emulation_overlay">Punch Hole cutout</string> + +</resources> + diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk new file mode 100644 index 000000000000..b6b6dd1c25bc --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := DisplayCutoutEmulationWaterfall + + +LOCAL_PRODUCT_MODULE := true + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := DisplayCutoutEmulationWaterfallOverlay +LOCAL_SDK_VERSION := current + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..2d5bb14f4dd3 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/AndroidManifest.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.internal.display.cutout.emulation.waterfall" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" + android:category="com.android.internal.display_cutout_emulation" + android:priority="1"/> + + <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/> +</manifest> diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml new file mode 100644 index 000000000000..df2f3d19626e --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape --> + <dimen name="quick_qs_offset_height">48dp</dimen> + <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 --> + <dimen name="quick_qs_total_height">176dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml new file mode 100644 index 000000000000..6f692c8021c0 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml @@ -0,0 +1,35 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Height of the status bar in portrait. The height should be + Max((status bar content height + waterfall top size), top cutout size) --> + <dimen name="status_bar_height_portrait">28dp</dimen> + <!-- Max((28 + 20), 0) = 48 --> + <dimen name="status_bar_height_landscape">48dp</dimen> + <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) --> + <dimen name="quick_qs_offset_height">28dp</dimen> + <!-- Total height of QQS (quick_qs_offset_height + 128) --> + <dimen name="quick_qs_total_height">156dp</dimen> + + <dimen name="waterfall_display_left_edge_size">20dp</dimen> + <dimen name="waterfall_display_top_edge_size">0dp</dimen> + <dimen name="waterfall_display_right_edge_size">20dp</dimen> + <dimen name="waterfall_display_bottom_edge_size">0dp</dimen> +</resources> + + diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml new file mode 100644 index 000000000000..ed073d0e244e --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <string name="display_cutout_emulation_overlay">Waterfall cutout</string> + +</resources>
\ No newline at end of file diff --git a/packages/overlays/IconShapePebbleOverlay/Android.mk b/packages/overlays/IconShapePebbleOverlay/Android.mk new file mode 100644 index 000000000000..c163bb91f76a --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/Android.mk @@ -0,0 +1,29 @@ +# +# Copyright 2020, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := IconShapePebble + +LOCAL_PRODUCT_MODULE := true + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := IconShapePebbleOverlay +LOCAL_SDK_VERSION := current + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..6842dde36264 --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml @@ -0,0 +1,29 @@ +<!-- +/** + * Copyright (c) 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.theme.icon.pebble" + android:versionCode="1" + android:versionName="1.0"> +<overlay + android:targetPackage="android" + android:targetName="IconShapeCustomization" + android:category="android.theme.customization.adaptive_icon_shape" + android:priority="1"/> + + <application android:label="@string/icon_shape_pebble_overlay" android:hasCode="false"/> +</manifest> diff --git a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml new file mode 100644 index 000000000000..2465fe015538 --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 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. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. --> + <string name="config_icon_mask" translatable="false">"MM55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"</string> + <!-- Flag indicating whether round icons should be parsed from the application manifest. --> + <bool name="config_useRoundIcon">false</bool> + <!-- Corner radius of system dialogs --> + <dimen name="config_dialogCornerRadius">8dp</dimen> + <!-- Corner radius for bottom sheet system dialogs --> + <dimen name="config_bottomDialogCornerRadius">16dp</dimen> + +</resources> + diff --git a/packages/overlays/IconShapePebbleOverlay/res/values/strings.xml b/packages/overlays/IconShapePebbleOverlay/res/values/strings.xml new file mode 100644 index 000000000000..aec4a82a6ba4 --- /dev/null +++ b/packages/overlays/IconShapePebbleOverlay/res/values/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Heart icon overlay --> + <string name="icon_shape_pebble_overlay" translatable="false">Pebble</string> + +</resources> diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java index c67fe9ffa916..1e8109cb393c 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java @@ -23,6 +23,8 @@ import android.util.Log; public class PacNative { private static final String TAG = "PacProxy"; + private static final PacNative sInstance = new PacNative(); + private String mCurrentPac; private boolean mIsActive; @@ -39,8 +41,12 @@ public class PacNative { System.loadLibrary("jni_pacprocessor"); } - PacNative() { + private PacNative() { + + } + public static PacNative getInstance() { + return sInstance; } public synchronized boolean startPacSupport() { diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java index 74391eb676d1..b006d6e1fa7b 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java @@ -31,43 +31,27 @@ import java.net.URL; public class PacService extends Service { private static final String TAG = "PacService"; - private PacNative mPacNative; - private ProxyServiceStub mStub; + private PacNative mPacNative = PacNative.getInstance(); + private ProxyServiceStub mStub = new ProxyServiceStub(); @Override public void onCreate() { super.onCreate(); - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } + mPacNative.startPacSupport(); } @Override public void onDestroy() { + mPacNative.stopPacSupport(); super.onDestroy(); - if (mPacNative != null) { - mPacNative.stopPacSupport(); - mPacNative = null; - mStub = null; - } } @Override public IBinder onBind(Intent intent) { - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } return mStub; } - private static class ProxyServiceStub extends IProxyService.Stub { - private final PacNative mPacNative; - - public ProxyServiceStub(PacNative pacNative) { - mPacNative = pacNative; - } + private class ProxyServiceStub extends IProxyService.Stub { @Override public String resolvePacFile(String host, String url) throws RemoteException { @@ -102,20 +86,12 @@ public class PacService extends Service { @Override public void startPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call startPacSystem"); - throw new SecurityException(); - } - mPacNative.startPacSupport(); + //TODO: remove } @Override public void stopPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call stopPacSystem"); - throw new SecurityException(); - } - mPacNative.stopPacSupport(); + //TODO: remove } } } |